1 #include "Animation_Control.h"
2 #include "Animation_Data.h"
3 #include "Isometric_Utils.h"
4 #include "Types.h"
5 #include "Soldier_Control.h"
6 #include "AI.h"
7 #include "AIInternals.h"
8 #include "OppList.h"
9 #include "Items.h"
10 #include "Rotting_Corpses.h"
11 #include "Soldier_Add.h"
12 #include "Debug.h"
13 
14 #include "ContentManager.h"
15 #include "GameInstance.h"
16 
17 
18 #define CAN_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER && s->ubBodyType != INFANT_MONSTER)
19 #define CAN_LISTEN_TO_CALL( s ) (s->ubBodyType != BLOODCAT && s->ubBodyType != LARVAE_MONSTER)
20 
21 enum CreatureCaller
22 {
23 	CALLER_FEMALE  = 0,
24 	CALLER_MALE,
25 	CALLER_INFANT,
26 	CALLER_QUEEN,
27 	NUM_CREATURE_CALLERS
28 };
29 
30 enum CreatureMobility
31 {
32 	CREATURE_MOBILE = 0,
33 	CREATURE_CRAWLER,
34 	CREATURE_IMMOBILE
35 };
36 
37 #define FRENZY_THRESHOLD 8
38 #define MAX_EAT_DIST 5
39 
40 static const INT8 gbCallPriority[NUM_CREATURE_CALLS][NUM_CREATURE_CALLERS] =
41 {
42 	{0, 0, 0 },//CALL_NONE
43 	{3, 5, 12},//CALL_1_PREY
44 	{5, 9, 12},//CALL_MULTIPLE_PREY
45 	{4, 7, 12},//CALL_ATTACKED
46 	{6, 9, 12},//CALL_CRIPPLED
47 };
48 
49 static const INT8 gbHuntCallPriority[NUM_CREATURE_CALLS] =
50 {
51 	4, //CALL_1_PREY
52 	5, //CALL_MULTIPLE_PREY
53 	7, //CALL_ATTACKED
54 	8  //CALL_CRIPPLED
55 };
56 
57 #define PRIORITY_DECR_DISTANCE 30
58 
59 #define CALL_1_OPPONENT CALL_1_PREY
60 #define CALL_MULTIPLE_OPPONENT CALL_MULTIPLE_PREY
61 
CreatureCall(SOLDIERTYPE * pCaller)62 void CreatureCall( SOLDIERTYPE * pCaller )
63 {
64 	UINT8  ubCallerType=0;
65 	INT8   bFullPriority;
66 	INT8   bPriority;
67 	UINT16 usDistToCaller;
68 	// communicate call to all creatures on map through ultrasonics
69 
70 	gTacticalStatus.Team[pCaller->bTeam].bAwareOfOpposition = TRUE;
71 	// bAction should be AI_ACTION_CREATURE_CALL (new)
72 	// usActionData is call enum #
73 	switch (pCaller->ubBodyType)
74 	{
75 		case ADULTFEMALEMONSTER:
76 		case YAF_MONSTER:
77 			ubCallerType = CALLER_FEMALE;
78 			break;
79 		case QUEENMONSTER:
80 			ubCallerType = CALLER_QUEEN;
81 			break;
82 		// need to add male
83 		case AM_MONSTER:
84 		case YAM_MONSTER:
85 			ubCallerType = CALLER_MALE;
86 			break;
87 		default:
88 			ubCallerType = CALLER_FEMALE;
89 			break;
90 	}
91 	if (pCaller->usActionData >= NUM_CREATURE_CALLS)
92 	{
93 		SLOGW("unexpected action data %u, defaulting call priority to 0", pCaller->usActionData);
94 		bFullPriority = 0;
95 	}
96 	else if (pCaller->bHunting) // which should only be set for females outside of the hive
97 	{
98 		bFullPriority = gbHuntCallPriority[pCaller->usActionData];
99 	}
100 	else
101 	{
102 		bFullPriority = gbCallPriority[pCaller->usActionData][ubCallerType];
103 	}
104 
105 	// OK, do animation based on body type...
106 	switch (pCaller->ubBodyType)
107 	{
108 		case ADULTFEMALEMONSTER:
109 		case YAF_MONSTER:
110 		case AM_MONSTER:
111 		case YAM_MONSTER:
112 
113 			EVENT_InitNewSoldierAnim( pCaller, MONSTER_UP, 0 , FALSE );
114 			break;
115 
116 		case QUEENMONSTER:
117 
118 			EVENT_InitNewSoldierAnim( pCaller, QUEEN_CALL, 0 , FALSE );
119 			break;
120 	}
121 
122 	FOR_EACH_IN_TEAM(pReceiver, pCaller->bTeam)
123 	{
124 		if (pReceiver->bInSector &&
125 			pReceiver->bLife >= OKLIFE &&
126 			pReceiver != pCaller &&
127 			pReceiver->bAlertStatus < STATUS_BLACK &&
128 			pReceiver->ubBodyType != LARVAE_MONSTER &&
129 			pReceiver->ubBodyType != INFANT_MONSTER &&
130 			pReceiver->ubBodyType != QUEENMONSTER)
131 		{
132 			usDistToCaller = PythSpacesAway( pReceiver->sGridNo, pCaller->sGridNo );
133 			bPriority = bFullPriority - (INT8) (usDistToCaller / PRIORITY_DECR_DISTANCE);
134 			if (bPriority > pReceiver->bCallPriority)
135 			{
136 				pReceiver->bCallPriority = bPriority;
137 				pReceiver->bAlertStatus = STATUS_RED; // our status can't be more than red to begin with
138 				pReceiver->ubCaller = pCaller->ubID;
139 				pReceiver->sCallerGridNo = pCaller->sGridNo;
140 				pReceiver->bCallActedUpon = FALSE;
141 				CancelAIAction(pReceiver);
142 				if ((bPriority > FRENZY_THRESHOLD) && (pReceiver->ubBodyType == ADULTFEMALEMONSTER || pReceiver->ubBodyType == YAF_MONSTER))
143 				{
144 					// go berzerk!
145 					pReceiver->bFrenzied = TRUE;
146 				}
147 			}
148 		}
149 	}
150 }
151 
152 
CreatureDecideActionGreen(SOLDIERTYPE * pSoldier)153 static INT8 CreatureDecideActionGreen(SOLDIERTYPE* pSoldier)
154 {
155 	INT32 iChance, iSneaky = 10;
156 	//INT8  bInWater;
157 	INT8  bInGas;
158 
159 	//bInWater = MercInWater(pSoldier);
160 
161 	// NB creatures would ignore smoke completely :-)
162 
163 	if ( pSoldier->bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
164 	{
165 		return( AI_ACTION_NONE );
166 	}
167 
168 	bInGas = InGas( pSoldier, pSoldier->sGridNo );
169 
170 	if (pSoldier->bMobility == CREATURE_MOBILE)
171 	{
172 
173 		if (TrackScent( pSoldier ))
174 		{
175 			return( AI_ACTION_TRACK );
176 		}
177 
178 		////////////////////////////////////////////////////////////////////////////
179 		// POINT PATROL: move towards next point unless getting a bit winded
180 		////////////////////////////////////////////////////////////////////////////
181 
182 		// this takes priority over water/gas checks, so that point patrol WILL work
183 		// from island to island, and through gas covered areas, too
184 		if ((pSoldier->bOrders == POINTPATROL) && (pSoldier->bBreath >= 50))
185 		{
186 			if (PointPatrolAI(pSoldier))
187 			{
188 				if (!gfTurnBasedAI)
189 				{
190 					// pause at the end of the walk!
191 					pSoldier->bNextAction = AI_ACTION_WAIT;
192 					pSoldier->usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
193 				}
194 
195 				return(AI_ACTION_POINT_PATROL);
196 			}
197 		}
198 
199 		if ((pSoldier->bOrders == RNDPTPATROL) && (pSoldier->bBreath >=50))
200 		{
201 			if (RandomPointPatrolAI(pSoldier))
202 			{
203 				if (!gfTurnBasedAI)
204 				{
205 					// pause at the end of the walk!
206 					pSoldier->bNextAction = AI_ACTION_WAIT;
207 					pSoldier->usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
208 				}
209 
210 				return(AI_ACTION_POINT_PATROL);
211 			}
212 		}
213 
214 		////////////////////////////////////////////////////////////////////////////
215 		// WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
216 		////////////////////////////////////////////////////////////////////////////
217 
218 		if ( /*bInWater || */ bInGas)
219 		{
220 			pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
221 
222 			if (pSoldier->usActionData != NOWHERE)
223 			{
224 				return(AI_ACTION_LEAVE_WATER_GAS);
225 			}
226 		}
227 	}
228 
229 	////////////////////////////////////////////////////////////////////////
230 	// REST IF RUNNING OUT OF BREATH
231 	////////////////////////////////////////////////////////////////////////
232 
233 	// if our breath is running a bit low, and we're not in the way or in water
234 	if ((pSoldier->bBreath < 75) /*&& !bInWater*/)
235 	{
236 		// take a breather for gods sake!
237 		pSoldier->usActionData = NOWHERE;
238 		return(AI_ACTION_NONE);
239 	}
240 
241 	////////////////////////////////////////////////////////////////////////////
242 	// RANDOM PATROL:  determine % chance to start a new patrol route
243 	////////////////////////////////////////////////////////////////////////////
244 
245 	if (pSoldier->bMobility != CREATURE_IMMOBILE )
246 	{
247 		iChance = 25;
248 
249 		// set base chance according to orders
250 		switch (pSoldier->bOrders)
251 		{
252 			case STATIONARY:     iChance += -20;  break;
253 			case ONGUARD:        iChance += -15;  break;
254 			case ONCALL:                          break;
255 			case CLOSEPATROL:    iChance += +15;  break;
256 			case RNDPTPATROL:
257 			case POINTPATROL:    iChance  =   0;  break;
258 			case FARPATROL:      iChance += +25;  break;
259 			case SEEKENEMY:      iChance += -10;  break;
260 		}
261 
262 		// modify chance of patrol (and whether it's a sneaky one) by attitude
263 		switch (pSoldier->bAttitude)
264 		{
265 			case DEFENSIVE:      iChance += -10;                 break;
266 			case BRAVESOLO:      iChance +=   5;                 break;
267 			case BRAVEAID:                                       break;
268 			case CUNNINGSOLO:    iChance +=   5;  iSneaky += 10; break;
269 			case CUNNINGAID:                      iSneaky +=  5; break;
270 			case AGGRESSIVE:     iChance +=  10;  iSneaky += -5; break;
271 		}
272 
273 		// reduce chance for any injury, less likely to wander around when hurt
274 		iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
275 
276 		// reduce chance if breath is down, less likely to wander around when tired
277 		iChance -= (100 - pSoldier->bBreath);
278 
279 		// if we're in water with land miles (> 25 tiles) away,
280 		// OR if we roll under the chance calculated
281 		if ( /*bInWater ||*/ ((INT16) PreRandom(100) < iChance))
282 		{
283 			pSoldier->usActionData = RandDestWithinRange(pSoldier);
284 
285 			if (pSoldier->usActionData != NOWHERE)
286 			{
287 				if (!gfTurnBasedAI)
288 				{
289 					// pause at the end of the walk!
290 					pSoldier->bNextAction = AI_ACTION_WAIT;
291 					pSoldier->usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
292 					if (pSoldier->bMobility == CREATURE_CRAWLER)
293 					{
294 						pSoldier->usNextActionData *= 2;
295 					}
296 				}
297 
298 				return(AI_ACTION_RANDOM_PATROL);
299 			}
300 		}
301 
302 		/*
303 		if (pSoldier->bMobility == CREATURE_MOBILE)
304 		{
305 			////////////////////////////////////////////////////////////////////////////
306 			// SEEK FRIEND: determine %chance for man to pay a friendly visit
307 			////////////////////////////////////////////////////////////////////////////
308 			iChance = 25;
309 
310 			// set base chance and maximum seeking distance according to orders
311 			switch (pSoldier->bOrders)
312 			{
313 				case STATIONARY:     iChance += -20; break;
314 				case ONGUARD:        iChance += -15; break;
315 				case ONCALL:                         break;
316 				case CLOSEPATROL:    iChance += +10; break;
317 				case RNDPTPATROL:
318 				case POINTPATROL:    iChance  = -10; break;
319 				case FARPATROL:      iChance += +20; break;
320 				case SEEKENEMY:      iChance += -10; break;
321 			}
322 
323 			// modify for attitude
324 			switch (pSoldier->bAttitude)
325 			{
326 				case DEFENSIVE:                       break;
327 				case BRAVESOLO:      iChance /= 2;    break;  // loners
328 				case BRAVEAID:       iChance += 10;   break;  // friendly
329 				case CUNNINGSOLO:    iChance /= 2;    break;  // loners
330 				case CUNNINGAID:     iChance += 10;   break;  // friendly
331 				case AGGRESSIVE:                      break;
332 			}
333 
334 			// reduce chance for any injury, less likely to wander around when hurt
335 			iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
336 
337 			// reduce chance if breath is down
338 			iChance -= (100 - pSoldier->bBreath);         // very likely to wait when exhausted
339 
340 			if ((INT16) PreRandom(100) < iChance)
341 			{
342 				if (RandomFriendWithin(pSoldier))
343 				{
344 					if (!gfTurnBasedAI)
345 					{
346 						// pause at the end of the walk!
347 						pSoldier->bNextAction = AI_ACTION_WAIT;
348 						pSoldier->usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
349 					}
350 
351 					return(AI_ACTION_SEEK_FRIEND);
352 				}
353 			}
354 		}
355 		*/
356 
357 		////////////////////////////////////////////////////////////////////////////
358 		// LOOK AROUND: determine %chance for man to turn in place
359 		////////////////////////////////////////////////////////////////////////////
360 
361 		// avoid 2 consecutive random turns in a row
362 		if (pSoldier->bLastAction != AI_ACTION_CHANGE_FACING && (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints))
363 		{
364 			iChance = 25;
365 
366 			// set base chance according to orders
367 			if (pSoldier->bOrders == STATIONARY)
368 				iChance += 25;
369 
370 			if (pSoldier->bOrders == ONGUARD)
371 				iChance += 20;
372 
373 			if (pSoldier->bAttitude == DEFENSIVE)
374 				iChance += 25;
375 
376 			if ((INT16)PreRandom(100) < iChance)
377 			{
378 				// roll random directions (stored in actionData) until different from current
379 				do
380 				{
381 					// if man has a LEGAL dominant facing, and isn't facing it, he will turn
382 					// back towards that facing 50% of the time here (normally just enemies)
383 					if ((pSoldier->bDominantDir >= 0) && (pSoldier->bDominantDir <= 8) &&
384 						(pSoldier->bDirection != pSoldier->bDominantDir) && PreRandom(2))
385 					{
386 						pSoldier->usActionData = pSoldier->bDominantDir;
387 					}
388 					else
389 					{
390 						pSoldier->usActionData = (UINT16)PreRandom(8);
391 					}
392 				}
393 				while (pSoldier->usActionData == pSoldier->bDirection);
394 
395 				if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->usActionData ) )
396 
397 				//InternalIsValidStance( pSoldier, (INT8) pSoldier->usActionData, ANIM_STAND ) )
398 				{
399 					if (!gfTurnBasedAI)
400 					{
401 						// pause at the end of the turn!
402 						pSoldier->bNextAction = AI_ACTION_WAIT;
403 						pSoldier->usNextActionData = (UINT16) REALTIME_CREATURE_AI_DELAY;
404 					}
405 
406 					return(AI_ACTION_CHANGE_FACING);
407 				}
408 			}
409 		}
410 	}
411 
412 	////////////////////////////////////////////////////////////////////////////
413 	// NONE:
414 	////////////////////////////////////////////////////////////////////////////
415 
416 	// by default, if everything else fails, just stands in place without turning
417 	pSoldier->usActionData = NOWHERE;
418 
419 	return(AI_ACTION_NONE);
420 }
421 
422 
CreatureDecideActionYellow(SOLDIERTYPE * pSoldier)423 static INT8 CreatureDecideActionYellow(SOLDIERTYPE* pSoldier)
424 {
425 	// monster AI - heard something
426 	INT16 sNoiseGridNo;
427 	INT32 iNoiseValue;
428 	INT32 iChance, iSneaky;
429 	BOOLEAN fClimb;
430 	BOOLEAN fReachable;
431 	//INT16 sClosestFriend;
432 
433 	if ( pSoldier->bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
434 	{
435 		return( AI_ACTION_NONE );
436 	}
437 
438 	// determine the most important noise heard, and its relative value
439 	sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue,&fClimb, &fReachable);
440 
441 	if (sNoiseGridNo == NOWHERE)
442 	{
443 		// then we have no business being under YELLOW status any more!
444 		return(AI_ACTION_NONE);
445 	}
446 
447 	////////////////////////////////////////////////////////////////////////////
448 	// LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise
449 	////////////////////////////////////////////////////////////////////////////
450 
451 	if (pSoldier->bMobility != CREATURE_IMMOBILE)
452 	{
453 		// determine direction from this soldier in which the noise lies
454 		const UINT8 ubNoiseDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sNoiseGridNo);
455 
456 		// if soldier is not already facing in that direction,
457 		// and the noise source is close enough that it could possibly be seen
458 		if ((GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) && (pSoldier->bDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= STRAIGHT)
459 		{
460 			// set base chance according to orders
461 			if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
462 				iChance = 60;
463 			else           // all other orders
464 				iChance = 35;
465 
466 			if (pSoldier->bAttitude == DEFENSIVE)
467 				iChance += 15;
468 
469 			if ((INT16)PreRandom(100) < iChance)
470 			{
471 				pSoldier->usActionData = ubNoiseDir;
472 				//if ( InternalIsValidStance( pSoldier, (INT8) pSoldier->usActionData, ANIM_STAND ) )
473 				if ( ValidCreatureTurn( pSoldier, (INT8) pSoldier->usActionData ) )
474 				{
475 					return(AI_ACTION_CHANGE_FACING);
476 				}
477 			}
478 		}
479 	}
480 
481 	////////////////////////////////////////////////////////////////////////
482 	// REST IF RUNNING OUT OF BREATH
483 	////////////////////////////////////////////////////////////////////////
484 
485 	// if our breath is running a bit low, and we're not in water
486 	if ((pSoldier->bBreath < 25) /*&& !MercInWater(pSoldier) */ )
487 	{
488 		// take a breather for gods sake!
489 		pSoldier->usActionData = NOWHERE;
490 		return(AI_ACTION_NONE);
491 	}
492 
493 	if (pSoldier->bMobility != CREATURE_IMMOBILE && fReachable)
494 	{
495 		////////////////////////////////////////////////////////////////////////////
496 		// SEEK NOISE
497 		////////////////////////////////////////////////////////////////////////////
498 
499 		// remember that noise value is negative, and closer to 0 => more important!
500 		iChance = 75 + iNoiseValue;
501 		iSneaky = 30;
502 
503 		// set base chance according to orders
504 		switch (pSoldier->bOrders)
505 		{
506 			case STATIONARY:     iChance += -20;  break;
507 			case ONGUARD:        iChance += -15;  break;
508 			case ONCALL:                          break;
509 			case CLOSEPATROL:    iChance += -10;  break;
510 			case RNDPTPATROL:
511 			case POINTPATROL:                     break;
512 			case FARPATROL:      iChance +=  10;  break;
513 			case SEEKENEMY:      iChance +=  25;  break;
514 		}
515 
516 		// modify chance of patrol (and whether it's a sneaky one) by attitude
517 		switch (pSoldier->bAttitude)
518 		{
519 			case DEFENSIVE:      iChance += -10;  iSneaky +=  15;  break;
520 			case BRAVESOLO:      iChance +=  10;                   break;
521 			case BRAVEAID:       iChance +=   5;                   break;
522 			case CUNNINGSOLO:    iChance +=   5;  iSneaky +=  30;  break;
523 			case CUNNINGAID:                      iSneaky +=  30;  break;
524 			case AGGRESSIVE:     iChance +=  20;  iSneaky += -10;  break;
525 		}
526 
527 		// reduce chance if breath is down, less likely to wander around when tired
528 		iChance -= (100 - pSoldier->bBreath);
529 
530 		if ((INT16) PreRandom(100) < iChance)
531 		{
532 			pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo, AI_ACTION_SEEK_NOISE);
533 
534 			if (pSoldier->usActionData != NOWHERE)
535 			{
536 				return(AI_ACTION_SEEK_NOISE);
537 			}
538 		}
539 		// Okay, we're not following up on the noise... but let's follow any
540 		// scent trails available
541 		if (TrackScent( pSoldier ))
542 		{
543 			return( AI_ACTION_TRACK );
544 		}
545 	}
546 
547 
548 
549 	////////////////////////////////////////////////////////////////////////////
550 	// DO NOTHING: Not enough points left to move, so save them for next turn
551 	////////////////////////////////////////////////////////////////////////////
552 	// by default, if everything else fails, just stands in place without turning
553 	pSoldier->usActionData = NOWHERE;
554 	return(AI_ACTION_NONE);
555 }
556 
557 
CreatureDecideActionRed(SOLDIERTYPE * pSoldier,UINT8 ubUnconsciousOK)558 static INT8 CreatureDecideActionRed(SOLDIERTYPE* pSoldier, UINT8 ubUnconsciousOK)
559 {
560 	// monster AI - hostile mammals somewhere around!
561 	INT16 iChance, sClosestOpponent /*,sClosestOpponent,sClosestFriend*/;
562 	INT16 sClosestDisturbance;
563 	INT16 sDistVisible;
564 	//INT8 bInWater;
565 	INT8 bInGas;
566 	BOOLEAN fChangeLevel;
567 
568 	// if we have absolutely no action points, we can't do a thing under RED!
569 	if (!pSoldier->bActionPoints)
570 	{
571 		pSoldier->usActionData = NOWHERE;
572 		return(AI_ACTION_NONE);
573 	}
574 
575 	if ( pSoldier->bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
576 	{
577 		return( AI_ACTION_NONE );
578 	}
579 
580 
581 	// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
582 	const UINT8 ubCanMove = (pSoldier->bMobility != CREATURE_IMMOBILE && pSoldier->bActionPoints >= MinPtsToMove(pSoldier));
583 
584 	// determine if we happen to be in water (in which case we're in BIG trouble!)
585 	//bInWater = MercInWater(pSoldier);
586 
587 	// check if standing in tear gas without a gas mask on
588 	bInGas = InGas( pSoldier, pSoldier->sGridNo );
589 
590 
591 	////////////////////////////////////////////////////////////////////////////
592 	// WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
593 	////////////////////////////////////////////////////////////////////////////
594 
595 	if (bInGas && ubCanMove)
596 	{
597 		pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
598 
599 		if (pSoldier->usActionData != NOWHERE)
600 		{
601 			return(AI_ACTION_LEAVE_WATER_GAS);
602 		}
603 	}
604 
605 	////////////////////////////////////////////////////////////////////////////
606 	// CALL FOR AID IF HURT
607 	////////////////////////////////////////////////////////////////////////////
608 	if ( CAN_CALL( pSoldier ) )
609 	{
610 		if ((pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
611 		{
612 			if (pSoldier->bLife < pSoldier->bOldLife)
613 			{
614 				// got injured, maybe call
615 				if ((pSoldier->bOldLife == pSoldier->bLifeMax) && (pSoldier->bOldLife - pSoldier->bLife > 10))
616 				{
617 					// hurt for first time!
618 					pSoldier->usActionData = CALL_CRIPPLED;
619 					pSoldier->bOldLife = pSoldier->bLife;  // don't want to call more than once
620 					return(AI_ACTION_CREATURE_CALL);
621 				}
622 				else if (pSoldier->bLifeMax / pSoldier->bLife > 2)
623 				{
624 					// crippled, 1/3 or less health!
625 					pSoldier->usActionData = CALL_ATTACKED;
626 					pSoldier->bOldLife = pSoldier->bLife;  // don't want to call more than once
627 					return(AI_ACTION_CREATURE_CALL);
628 				}
629 			}
630 		}
631 	}
632 
633 
634 	////////////////////////////////////////////////////////////////////////
635 	// CROUCH & REST IF RUNNING OUT OF BREATH
636 	////////////////////////////////////////////////////////////////////////
637 
638 	// if our breath is running a bit low, and we're not in water or under fire
639 	if ((pSoldier->bBreath < 25) /*&& !bInWater*/ && !pSoldier->bUnderFire)
640 	{
641 		pSoldier->usActionData = NOWHERE;
642 		return(AI_ACTION_NONE);
643 	}
644 
645 	////////////////////////////////////////////////////////////////////////////
646 	// CALL IN SIGHTING: determine %chance to call others and report contact
647 	////////////////////////////////////////////////////////////////////////////
648 
649 	// if we're a computer merc, and we have the action points remaining to RADIO
650 	// (we never want NPCs to choose to radio if they would have to wait a turn)
651 	if ( CAN_CALL( pSoldier ) && (!gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) )
652 	{
653 		if ((pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
654 		{
655 			// if there hasn't been a general sighting call sent yet
656 
657 			// might want to check the specifics of who we see
658 			iChance = 20;
659 
660 			if (iChance)
661 			{
662 				if ((INT16) PreRandom(100) < iChance)
663 				{
664 					SLOGD("%s decides to call an alert!", pSoldier->name.c_str());
665 					pSoldier->usActionData = CALL_1_PREY;
666 					return(AI_ACTION_CREATURE_CALL);
667 				}
668 			}
669 		}
670 	}
671 
672 	if ( pSoldier->bMobility != CREATURE_IMMOBILE )
673 	{
674 		if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
675 		{
676 			// probably a baby bug... run away! run away!
677 			// look for best place to RUN AWAY to (farthest from the closest threat)
678 			pSoldier->usActionData = FindSpotMaxDistFromOpponents( pSoldier );
679 
680 			if (pSoldier->usActionData != NOWHERE)
681 			{
682 				return(AI_ACTION_RUN_AWAY);
683 			}
684 			else
685 			{
686 				return( AI_ACTION_NONE );
687 			}
688 
689 		}
690 
691 		// Respond to call if any
692 		if ( CAN_LISTEN_TO_CALL( pSoldier ) && pSoldier->ubCaller != NOBODY )
693 		{
694 			if ( PythSpacesAway( pSoldier->sGridNo, pSoldier->sCallerGridNo ) <= STOPSHORTDIST )
695 			{
696 				// call completed... hmm, nothing found
697 				pSoldier->ubCaller = NOBODY;
698 			}
699 			else
700 			{
701 				pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, pSoldier->sCallerGridNo, -1, AI_ACTION_SEEK_FRIEND, FLAG_STOPSHORT);
702 
703 				if (pSoldier->usActionData != NOWHERE)
704 				{
705 					return(AI_ACTION_SEEK_FRIEND);
706 				}
707 			}
708 		}
709 
710 		// get the location of the closest reachable opponent
711 		sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fChangeLevel);
712 		// if there is an opponent reachable
713 		if (sClosestDisturbance != NOWHERE)
714 		{
715 			//////////////////////////////////////////////////////////////////////
716 			// SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT
717 			//////////////////////////////////////////////////////////////////////
718 
719 			// try to move towards him
720 			pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AI_ACTION_SEEK_OPPONENT);
721 
722 			// if it's possible
723 			if (pSoldier->usActionData != NOWHERE)
724 			{
725 				return(AI_ACTION_SEEK_OPPONENT);
726 			}
727 		}
728 
729 		////////////////////////////////////////////////////////////////////////////
730 		// TAKE A BITE, PERHAPS
731 		////////////////////////////////////////////////////////////////////////////
732 		if (pSoldier->bHunting)
733 		{
734 			pSoldier->usActionData = FindNearestRottingCorpse( pSoldier );
735 			// need smell/visibility check?
736 			if (PythSpacesAway( pSoldier->sGridNo, pSoldier->usActionData) < MAX_EAT_DIST )
737 			{
738 				const INT16 sGridNo = FindAdjacentGridEx(pSoldier, pSoldier->usActionData, NULL, NULL, FALSE, FALSE);
739 				if ( sGridNo != -1 )
740 				{
741 					pSoldier->usActionData = sGridNo;
742 					return( AI_ACTION_APPROACH_MERC );
743 				}
744 			}
745 		}
746 
747 		////////////////////////////////////////////////////////////////////////////
748 		// TRACK A SCENT, IF ONE IS PRESENT
749 		////////////////////////////////////////////////////////////////////////////
750 		if (TrackScent( pSoldier ))
751 		{
752 			return( AI_ACTION_TRACK );
753 		}
754 
755 
756 		////////////////////////////////////////////////////////////////////////////
757 		// LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
758 		////////////////////////////////////////////////////////////////////////////
759 		if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
760 		{
761 			// determine the location of the known closest opponent
762 			// (don't care if he's conscious, don't care if he's reachable at all)
763 			sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
764 
765 			if (sClosestOpponent != NOWHERE)
766 			{
767 				// determine direction from this soldier to the closest opponent
768 				const UINT8 ubOpponentDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestOpponent);
769 
770 				// if soldier is not already facing in that direction,
771 				// and the opponent is close enough that he could possibly be seen
772 				// note, have to change this to use the level returned from ClosestKnownOpponent
773 				sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sClosestOpponent, 0 );
774 
775 				if ((pSoldier->bDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible))
776 				{
777 					// set base chance according to orders
778 					if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
779 						iChance = 50;
780 					else           // all other orders
781 						iChance = 25;
782 
783 					if (pSoldier->bAttitude == DEFENSIVE)
784 						iChance += 25;
785 
786 					//if ( (INT16)PreRandom(100) < iChance && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_STAND ) )
787 					if ( (INT16)PreRandom(100) < iChance && ValidCreatureTurn( pSoldier, ubOpponentDir ) )
788 					{
789 						pSoldier->usActionData = ubOpponentDir;
790 						return(AI_ACTION_CHANGE_FACING);
791 					}
792 				}
793 			}
794 		}
795 	}
796 
797 	////////////////////////////////////////////////////////////////////////////
798 	// LEAVE THE SECTOR
799 	////////////////////////////////////////////////////////////////////////////
800 
801 	// NOT IMPLEMENTED
802 
803 	////////////////////////////////////////////////////////////////////////////
804 	// DO NOTHING: Not enough points left to move, so save them for next turn
805 	////////////////////////////////////////////////////////////////////////////
806 	pSoldier->usActionData = NOWHERE;
807 	return(AI_ACTION_NONE);
808 }
809 
810 
CreatureDecideActionBlack(SOLDIERTYPE * pSoldier)811 static INT8 CreatureDecideActionBlack(SOLDIERTYPE* pSoldier)
812 {
813 	// monster AI - hostile mammals in sense range
814 	INT16      sClosestOpponent;
815 	INT16      sClosestDisturbance;
816 	UINT8      ubMinAPCost,ubCanMove/*,bInWater*/,bInGas;
817 	UINT8      ubBestAttackAction;
818 	INT8       bCanAttack;
819 	INT8       bSpitIn, bWeaponIn;
820 	UINT32     uiChance;
821 	ATTACKTYPE BestShot, BestStab, BestAttack, CurrStab;
822 	BOOLEAN    fRunAway = FALSE;
823 	BOOLEAN    fChangeLevel;
824 
825 	// if we have absolutely no action points, we can't do a thing under BLACK!
826 	if (!pSoldier->bActionPoints)
827 	{
828 		pSoldier->usActionData = NOWHERE;
829 		return(AI_ACTION_NONE);
830 	}
831 
832 	if ( pSoldier->bMobility == CREATURE_CRAWLER && pSoldier->bActionPoints < pSoldier->bInitialActionPoints)
833 	{
834 		return( AI_ACTION_NONE );
835 	}
836 
837 	////////////////////////////////////////////////////////////////////////////
838 	// CALL FOR AID IF HURT OR IF OTHERS ARE UNAWARE
839 	////////////////////////////////////////////////////////////////////////////
840 
841 	if ( CAN_CALL( pSoldier ) )
842 	{
843 		if ((pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1))
844 		{
845 			if (pSoldier->bLife < pSoldier->bOldLife)
846 			{
847 				// got injured, maybe call
848 				/*
849 				// don't call when crippled and have target... save breath for attacking!
850 				if ((pSoldier->bOldLife == pSoldier->bLifeMax) && (pSoldier->bOldLife - pSoldier->bLife > 10))
851 				{
852 					// hurt for first time!
853 					pSoldier->usActionData = CALL_CRIPPLED;
854 					pSoldier->bOldLife = pSoldier->bLife;  // don't want to call more than once
855 					return(AI_ACTION_CREATURE_CALL);
856 				}
857 				else
858 				*/
859 				if (pSoldier->bLifeMax / pSoldier->bLife > 2)
860 				{
861 					// crippled, 1/3 or less health!
862 					pSoldier->usActionData = CALL_ATTACKED;
863 					pSoldier->bOldLife = pSoldier->bLife;  // don't want to call more than once
864 					return(AI_ACTION_CREATURE_CALL);
865 				}
866 			}
867 			else
868 			{
869 				if (!(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition))
870 				{
871 					if (pSoldier->ubBodyType == QUEENMONSTER)
872 					{
873 						uiChance = 100;
874 					}
875 					else
876 					{
877 						uiChance = 20 * pSoldier->bOppCnt;
878 					}
879 					if ( Random( 100 ) < uiChance )
880 					{
881 						// alert! alert!
882 						if (pSoldier->bOppCnt > 1)
883 						{
884 							pSoldier->usActionData = CALL_MULTIPLE_PREY;
885 						}
886 						else
887 						{
888 							pSoldier->usActionData = CALL_1_PREY;
889 						}
890 						return(AI_ACTION_CREATURE_CALL);
891 					}
892 				}
893 			}
894 		}
895 	}
896 
897 	// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
898 	ubCanMove = ((pSoldier->bMobility != CREATURE_IMMOBILE) && (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)));
899 
900 	// determine if we happen to be in water (in which case we're in BIG trouble!)
901 	//bInWater = MercInWater(pSoldier);
902 
903 	// check if standing in tear gas without a gas mask on
904 	bInGas = InGas( pSoldier, pSoldier->sGridNo );
905 
906 
907 	////////////////////////////////////////////////////////////////////////////
908 	// IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY
909 	////////////////////////////////////////////////////////////////////////////
910 
911 	// if we're desperately short on breath (it's OK if we're in water, though!)
912 	if (bInGas || (pSoldier->bBreath < 5))
913 	{
914 		// if soldier has enough APs left to move at least 1 square's worth
915 		if (ubCanMove)
916 		{
917 			// look for best place to RUN AWAY to (farthest from the closest threat)
918 			pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
919 
920 			if (pSoldier->usActionData != NOWHERE)
921 			{
922 				return(AI_ACTION_RUN_AWAY);
923 			}
924 		}
925 	}
926 
927 
928 	////////////////////////////////////////////////////////////////////////////
929 	// STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND
930 	////////////////////////////////////////////////////////////////////////////
931 
932 	// if soldier in water/gas has enough APs left to move at least 1 square
933 	if ((/*bInWater ||*/ bInGas) && ubCanMove)
934 	{
935 		pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
936 
937 		if (pSoldier->usActionData != NOWHERE)
938 		{
939 			return(AI_ACTION_LEAVE_WATER_GAS);
940 		}
941 	}
942 
943 	////////////////////////////////////////////////////////////////////////////
944 	// SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY
945 	////////////////////////////////////////////////////////////////////////////
946 
947 	// NPCs in water/tear gas without masks are not permitted to shoot/stab/throw
948 	if ((pSoldier->bActionPoints < 2) /*|| bInWater*/ || bInGas)
949 	{
950 		bCanAttack = FALSE;
951 	}
952 	else
953 	{
954 		bCanAttack = CanNPCAttack(pSoldier);
955 		if (bCanAttack != TRUE)
956 		{
957 			if ( bCanAttack == NOSHOOT_NOAMMO )
958 			{
959 				pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE;
960 
961 				// try to find a bladed weapon
962 				if (pSoldier->ubBodyType == QUEENMONSTER)
963 				{
964 					bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES );
965 				}
966 				else
967 				{
968 					bWeaponIn = FindObjClass( pSoldier, IC_BLADE );
969 				}
970 
971 				if ( bWeaponIn != NO_SLOT )
972 				{
973 					RearrangePocket(pSoldier,HANDPOS,bWeaponIn,FOREVER);
974 					bCanAttack = TRUE;
975 				}
976 				else
977 				{
978 					// infants who exhaust their spit should flee!
979 					fRunAway = TRUE;
980 					bCanAttack = FALSE;
981 				}
982 
983 			}
984 			else
985 			{
986 				bCanAttack = FALSE;
987 			}
988 
989 		}
990 	}
991 
992 
993 	BestShot.ubPossible  = FALSE;	// by default, assume Shooting isn't possible
994 	BestStab.ubPossible  = FALSE;	// by default, assume Stabbing isn't possible
995 
996 	BestAttack = ATTACKTYPE{};
997 
998 	bSpitIn = NO_SLOT;
999 
1000 
1001 	// if we are able attack
1002 	if (bCanAttack)
1003 	{
1004 		//////////////////////////////////////////////////////////////////////////
1005 		// FIRE A GUN AT AN OPPONENT
1006 		//////////////////////////////////////////////////////////////////////////
1007 
1008 		pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
1009 
1010 		bWeaponIn = FindObjClass( pSoldier, IC_GUN );
1011 
1012 		if (bWeaponIn != NO_SLOT)
1013 		{
1014 			if (GCM->getItem(pSoldier->inv[bWeaponIn].usItem)->getItemClass() == IC_GUN && pSoldier->inv[bWeaponIn].bGunStatus >= USABLE)
1015 			{
1016 				if (pSoldier->inv[bWeaponIn].ubGunShotsLeft > 0)
1017 				{
1018 					bSpitIn = bWeaponIn;
1019 					// if it's in another pocket, swap it into his hand temporarily
1020 					if (bWeaponIn != HANDPOS)
1021 					{
1022 						RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
1023 					}
1024 
1025 					// now it better be a gun, or the guy can't shoot (but has other attack(s))
1026 
1027 					// get the minimum cost to attack the same target with this gun
1028 					ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
1029 
1030 					// if we have enough action points to shoot with this gun
1031 					if (pSoldier->bActionPoints >= ubMinAPCost)
1032 					{
1033 						// look around for a worthy target (which sets BestShot.ubPossible)
1034 						CalcBestShot(pSoldier,&BestShot);
1035 
1036 						if (BestShot.ubPossible)
1037 						{
1038 							BestShot.bWeaponIn = bWeaponIn;
1039 
1040 							// if the selected opponent is not a threat (unconscious & !serviced)
1041 							// (usually, this means all the guys we see our unconscious, but, on
1042 							//  rare occasions, we may not be able to shoot a healthy guy, too)
1043 							const SOLDIERTYPE* const opp = BestShot.opponent;
1044 							if (opp->bLife < OKLIFE)
1045 							{
1046 								// if our attitude is NOT aggressive
1047 								if (pSoldier->bAttitude != AGGRESSIVE)
1048 								{
1049 									// get the location of the closest CONSCIOUS reachable opponent
1050 									sClosestDisturbance = ClosestReachableDisturbance(pSoldier,FALSE,&fChangeLevel);
1051 
1052 									// if we found one
1053 									if (sClosestDisturbance != NOWHERE)
1054 									{
1055 										// don't bother checking GRENADES/KNIVES, he can't have conscious targets
1056 										// then make decision as if at alert status RED, but make sure
1057 										// we don't try to SEEK OPPONENT the unconscious guy!
1058 										return(DecideActionRed(pSoldier,FALSE));
1059 									}
1060 									// else kill the guy, he could be the last opponent alive in this sector
1061 								}
1062 								// else aggressive guys will ALWAYS finish off unconscious opponents
1063 							}
1064 
1065 							// now we KNOW FOR SURE that we will do something (shoot, at least)
1066 							NPCDoesAct(pSoldier);
1067 
1068 						}
1069 					}
1070 					// if it was in his holster, swap it back into his holster for now
1071 					if (bWeaponIn != HANDPOS)
1072 					{
1073 						RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
1074 					}
1075 				}
1076 				else
1077 				{
1078 					// out of ammo! reload if possible!
1079 				}
1080 
1081 			}
1082 
1083 		}
1084 
1085 		//////////////////////////////////////////////////////////////////////////
1086 		// GO STAB AN OPPONENT WITH A KNIFE
1087 		//////////////////////////////////////////////////////////////////////////
1088 
1089 		// if soldier has a knife in his hand
1090 		if (pSoldier->ubBodyType == QUEENMONSTER)
1091 		{
1092 			bWeaponIn = FindObjClass( pSoldier, IC_TENTACLES );
1093 		}
1094 		else if ( pSoldier->ubBodyType == BLOODCAT )
1095 		{
1096 			// 1 in 3 attack with teeth, otherwise with claws
1097 			if ( PreRandom( 3 ) )
1098 			{
1099 				bWeaponIn = FindObj( pSoldier, BLOODCAT_CLAW_ATTACK );
1100 			}
1101 			else
1102 			{
1103 				bWeaponIn = FindObj( pSoldier, BLOODCAT_BITE );
1104 			}
1105 		}
1106 		else
1107 		{
1108 			if (bSpitIn != NO_SLOT && Random( 4 ) )
1109 			{
1110 				// spitters only consider a blade attack 1 time in 4
1111 				bWeaponIn = NO_SLOT;
1112 			}
1113 			else
1114 			{
1115 				bWeaponIn = FindObjClass( pSoldier, IC_BLADE );
1116 			}
1117 		}
1118 
1119 
1120 
1121 		BestStab.iAttackValue = 0;
1122 
1123 		// if the soldier does have a usable knife somewhere
1124 
1125 		// spitters don't always consider using their claws
1126 		if ( bWeaponIn != NO_SLOT )
1127 		{
1128 			// if it's in his holster, swap it into his hand temporarily
1129 			if (bWeaponIn != HANDPOS)
1130 			{
1131 				RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
1132 			}
1133 
1134 			// get the minimum cost to attack with this knife
1135 			ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
1136 
1137 			// if we can afford the minimum AP cost to stab with this knife weapon
1138 			if (pSoldier->bActionPoints >= ubMinAPCost)
1139 			{
1140 				// then look around for a worthy target (which sets BestStab.ubPossible)
1141 
1142 				if (pSoldier->ubBodyType == QUEENMONSTER)
1143 				{
1144 					CalcTentacleAttack( pSoldier, &CurrStab );
1145 				}
1146 				else
1147 				{
1148 					CalcBestStab(pSoldier, &CurrStab, TRUE);
1149 				}
1150 
1151 				if (CurrStab.ubPossible)
1152 				{
1153 					// now we KNOW FOR SURE that we will do something (stab, at least)
1154 					NPCDoesAct(pSoldier);
1155 				}
1156 
1157 				// if it was in his holster, swap it back into his holster for now
1158 				if (bWeaponIn != HANDPOS)
1159 				{
1160 					RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
1161 				}
1162 
1163 				if (CurrStab.iAttackValue > BestStab.iAttackValue)
1164 				{
1165 					CurrStab.bWeaponIn = bWeaponIn;
1166 					BestStab = CurrStab;
1167 				}
1168 
1169 			}
1170 
1171 		}
1172 
1173 		//////////////////////////////////////////////////////////////////////////
1174 		// CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE
1175 		//////////////////////////////////////////////////////////////////////////
1176 		if (BestShot.ubPossible)
1177 		{
1178 			BestAttack.iAttackValue = BestShot.iAttackValue;
1179 			ubBestAttackAction = AI_ACTION_FIRE_GUN;
1180 		}
1181 		else
1182 		{
1183 			BestAttack.iAttackValue = 0;
1184 			ubBestAttackAction = AI_ACTION_NONE;
1185 		}
1186 		if (BestStab.ubPossible && BestStab.iAttackValue > (BestAttack.iAttackValue * 12) / 10 )
1187 		{
1188 			BestAttack.iAttackValue = BestStab.iAttackValue;
1189 			ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
1190 		}
1191 
1192 		// if attack is still desirable (meaning it's also preferred to taking cover)
1193 		if (ubBestAttackAction != AI_ACTION_NONE)
1194 		{
1195 			// copy the information on the best action selected into BestAttack struct
1196 			switch (ubBestAttackAction)
1197 			{
1198 				case AI_ACTION_FIRE_GUN:   BestAttack = BestShot; break;
1199 				case AI_ACTION_KNIFE_MOVE: BestAttack = BestStab; break;
1200 			}
1201 
1202 			// if necessary, swap the weapon into the hand position
1203 			if (BestAttack.bWeaponIn != HANDPOS)
1204 			{
1205 				// IS THIS NOT BEING SET RIGHT?????
1206 				RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER);
1207 			}
1208 
1209 			//////////////////////////////////////////////////////////////////////////
1210 			// GO AHEAD & ATTACK!
1211 			//////////////////////////////////////////////////////////////////////////
1212 
1213 			pSoldier->usActionData = BestAttack.sTarget;
1214 			pSoldier->bAimTime			= BestAttack.ubAimTime;
1215 
1216 			if ( ubBestAttackAction == AI_ACTION_FIRE_GUN && BestAttack.ubChanceToReallyHit > 50 )
1217 			{
1218 				pSoldier->bAimShotLocation = AIM_SHOT_HEAD;
1219 			}
1220 			else
1221 			{
1222 				pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
1223 			}
1224 			SLOGD("%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n",
1225 				pSoldier->ubID, pSoldier->name.c_str(),
1226 				(ubBestAttackAction == AI_ACTION_FIRE_GUN)?"SHOOTS":((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE)?"TOSSES AT":"STABS"),
1227 				BestAttack.opponent, BestAttack.opponent->name.c_str(),
1228 				BestAttack.sTarget, BestAttack.ubAimTime);
1229 			return(ubBestAttackAction);
1230 		}
1231 	}
1232 
1233 
1234 
1235 	////////////////////////////////////////////////////////////////////////////
1236 	// CLOSE ON THE CLOSEST KNOWN OPPONENT or TURN TO FACE HIM
1237 	////////////////////////////////////////////////////////////////////////////
1238 
1239 	if ( !fRunAway )
1240 	{
1241 		if ( (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) )
1242 		{
1243 			// determine the location of the known closest opponent
1244 			// (don't care if he's conscious, don't care if he's reachable at all)
1245 			sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
1246 			// if we have a closest reachable opponent
1247 			if (sClosestOpponent != NOWHERE)
1248 			{
1249 				if ( ubCanMove && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) > 2 )
1250 				{
1251 					if ( bSpitIn != NO_SLOT )
1252 					{
1253 						pSoldier->usActionData = AdvanceToFiringRange( pSoldier, sClosestOpponent );
1254 						if (pSoldier->usActionData == NOWHERE)
1255 						{
1256 							pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT);
1257 						}
1258 					}
1259 					else
1260 					{
1261 						pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_SEEK_OPPONENT);
1262 					}
1263 				}
1264 				else
1265 				{
1266 					pSoldier->usActionData = NOWHERE;
1267 				}
1268 
1269 				if (pSoldier->usActionData != NOWHERE) // charge!
1270 				{
1271 					return( AI_ACTION_SEEK_OPPONENT );
1272 				}
1273 				else if (GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) // turn to face enemy
1274 				{
1275 					const INT8 bDirection = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestOpponent);
1276 
1277 					// if we're not facing towards him
1278 					if (pSoldier->bDirection != bDirection && ValidCreatureTurn( pSoldier, bDirection ) )
1279 					{
1280 						pSoldier->usActionData = bDirection;
1281 						return(AI_ACTION_CHANGE_FACING);
1282 					}
1283 				}
1284 			}
1285 		}
1286 	}
1287 	else
1288 	{
1289 		// run away!
1290 		if ( ubCanMove )
1291 		{
1292 			// look for best place to RUN AWAY to (farthest from the closest threat)
1293 			pSoldier->usActionData = FindSpotMaxDistFromOpponents( pSoldier );
1294 
1295 			if (pSoldier->usActionData != NOWHERE)
1296 			{
1297 				return(AI_ACTION_RUN_AWAY);
1298 			}
1299 		}
1300 
1301 	}
1302 	////////////////////////////////////////////////////////////////////////////
1303 	// DO NOTHING: Not enough points left to move, so save them for next turn
1304 	////////////////////////////////////////////////////////////////////////////
1305 	// by default, if everything else fails, just stand in place and wait
1306 	pSoldier->usActionData = NOWHERE;
1307 	return(AI_ACTION_NONE);
1308 }
1309 
1310 
1311 
1312 
CreatureDecideAction(SOLDIERTYPE * pSoldier)1313 INT8 CreatureDecideAction( SOLDIERTYPE *pSoldier )
1314 {
1315 	INT8 bAction = AI_ACTION_NONE;
1316 
1317 	switch (pSoldier->bAlertStatus)
1318 	{
1319 		case STATUS_GREEN:
1320 			bAction = CreatureDecideActionGreen(pSoldier);
1321 			break;
1322 
1323 		case STATUS_YELLOW:
1324 			bAction = CreatureDecideActionYellow(pSoldier);
1325 		break;
1326 
1327 		case STATUS_RED:
1328 			bAction = CreatureDecideActionRed(pSoldier, TRUE);
1329 			break;
1330 
1331 		case STATUS_BLACK:
1332 			bAction = CreatureDecideActionBlack(pSoldier);
1333 			break;
1334 	}
1335 	SLOGD("DecideAction: selected action %d, actionData %d\n\n",
1336 		bAction, pSoldier->usActionData);
1337 	return(bAction);
1338 }
1339 
CreatureDecideAlertStatus(SOLDIERTYPE * pSoldier)1340 void CreatureDecideAlertStatus( SOLDIERTYPE *pSoldier )
1341 {
1342 	INT8	bOldStatus;
1343 	INT32	iDummy;
1344 	BOOLEAN	fClimbDummy, fReachableDummy;
1345 
1346 	// THE FOUR (4) POSSIBLE ALERT STATUSES ARE:
1347 	// GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff
1348 	// YELLOW - Suspicious noise was heard personally
1349 	// RED - Either saw OPPONENTS in person, or definite contact had been called
1350 	// BLACK - Currently has one or more OPPONENTS in sight
1351 
1352 	// set mobility
1353 	switch (pSoldier->ubBodyType)
1354 	{
1355 		case ADULTFEMALEMONSTER:
1356 		case YAF_MONSTER:
1357 		case AM_MONSTER:
1358 		case YAM_MONSTER:
1359 		case INFANT_MONSTER:
1360 			pSoldier->bMobility = CREATURE_MOBILE;
1361 			break;
1362 		case QUEENMONSTER:
1363 			pSoldier->bMobility = CREATURE_IMMOBILE;
1364 			break;
1365 		case LARVAE_MONSTER:
1366 			pSoldier->bMobility = CREATURE_CRAWLER;
1367 			break;
1368 	}
1369 
1370 
1371 	if (pSoldier->ubBodyType == LARVAE_MONSTER)
1372 	{
1373 		// larvae never do anything much!
1374 		pSoldier->bAlertStatus = STATUS_GREEN;
1375 		return;
1376 	}
1377 
1378 	// save the man's previous status
1379 	bOldStatus = pSoldier->bAlertStatus;
1380 
1381 	// determine the current alert status for this category of man
1382 	if (pSoldier->bOppCnt > 0)        // opponent(s) in sight
1383 	{
1384 		// must search through list of people to see if any of them have
1385 		// attacked us, or do some check to see if we have been attacked
1386 		switch (bOldStatus)
1387 		{
1388 			case STATUS_GREEN:
1389 			case STATUS_YELLOW:
1390 				pSoldier->bAlertStatus = STATUS_BLACK;
1391 				break;
1392 			case STATUS_RED:
1393 			case STATUS_BLACK:
1394 				pSoldier->bAlertStatus = STATUS_BLACK;
1395 		}
1396 
1397 	}
1398 	else // no opponents are in sight
1399 	{
1400 		switch (bOldStatus)
1401 		{
1402 			case STATUS_BLACK:
1403 				// then drop back to RED status
1404 				pSoldier->bAlertStatus = STATUS_RED;
1405 				break;
1406 
1407 			case STATUS_RED:
1408 				// RED can never go back down below RED, only up to BLACK
1409 				break;
1410 
1411 			case STATUS_YELLOW:
1412 				// if all enemies have been RED alerted, or we're under fire
1413 				if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire)
1414 				{
1415 					pSoldier->bAlertStatus = STATUS_RED;
1416 				}
1417 				else
1418 				{
1419 					// if we are NOT aware of any uninvestigated noises right now
1420 					// and we are not currently in the middle of an action
1421 					// (could still be on his way heading to investigate a noise!)
1422 					if ((MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE) && !pSoldier->bActionInProgress)
1423 					{
1424 						// then drop back to GREEN status
1425 						pSoldier->bAlertStatus = STATUS_GREEN;
1426 					}
1427 				}
1428 				break;
1429 
1430 			case STATUS_GREEN:
1431 				// if all enemies have been RED alerted, or we're under fire
1432 				if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire)
1433 				{
1434 					pSoldier->bAlertStatus = STATUS_RED;
1435 				}
1436 				else
1437 				{
1438 					// if we ARE aware of any uninvestigated noises right now
1439 					if (MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) != NOWHERE)
1440 					{
1441 						// then move up to YELLOW status
1442 						pSoldier->bAlertStatus = STATUS_YELLOW;
1443 					}
1444 				}
1445 				break;
1446 		}
1447 		// otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN
1448 	}
1449 
1450 	// if the creatures alert status has changed in any way
1451 	if (pSoldier->bAlertStatus != bOldStatus)
1452 	{
1453 		// HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK
1454 		// if either status is < RED (ie. anything but RED->BLACK && BLACK->RED)
1455 		if ((bOldStatus < STATUS_RED) || (pSoldier->bAlertStatus < STATUS_RED))
1456 		{
1457 			// force a NEW action decision on next pass through HandleManAI()
1458 			SetNewSituation( pSoldier );
1459 		}
1460 
1461 		// if this guy JUST discovered that there were opponents here for sure...
1462 		if ((bOldStatus < STATUS_RED) && (pSoldier->bAlertStatus >= STATUS_RED))
1463 		{
1464 			// might want to make custom to let them go anywhere
1465 			CheckForChangingOrders(pSoldier);
1466 		}
1467 	}
1468 	else   // status didn't change
1469 	{
1470 		// if a guy on status GREEN or YELLOW is running low on breath
1471 		if (((pSoldier->bAlertStatus == STATUS_GREEN)  && (pSoldier->bBreath < 75)) ||
1472 			((pSoldier->bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50)))
1473 		{
1474 			// as long as he's not in water (standing on a bridge is OK)
1475 			if (!MercInWater(pSoldier))
1476 			{
1477 				// force a NEW decision so that he can get some rest
1478 				SetNewSituation( pSoldier );
1479 
1480 				// current action will be canceled. if noise is no longer important
1481 				if ((pSoldier->bAlertStatus == STATUS_YELLOW) &&
1482 					(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE))
1483 				{
1484 					// then drop back to GREEN status
1485 					pSoldier->bAlertStatus = STATUS_GREEN;
1486 					CheckForChangingOrders(pSoldier);
1487 				}
1488 			}
1489 		}
1490 	}
1491 }
1492 
1493 
CrowDecideActionRed(SOLDIERTYPE * pSoldier)1494 static INT8 CrowDecideActionRed(SOLDIERTYPE* pSoldier)
1495 {
1496 	// OK, Fly away!
1497 	//HandleCrowFlyAway( pSoldier );
1498 	if (!gfTurnBasedAI)
1499 	{
1500 		pSoldier->usActionData = 30000;
1501 		return( AI_ACTION_WAIT );
1502 	}
1503 	else
1504 	{
1505 		return( AI_ACTION_NONE );
1506 	}
1507 }
1508 
1509 
CrowDecideActionGreen(SOLDIERTYPE * pSoldier)1510 static INT8 CrowDecideActionGreen(SOLDIERTYPE* pSoldier)
1511 {
1512 	INT16 sCorpseGridNo;
1513 	INT16 sFacingDir;
1514 
1515 	// Look for a corse!
1516 	sCorpseGridNo = FindNearestRottingCorpse( pSoldier );
1517 
1518 	if ( sCorpseGridNo != NOWHERE )
1519 	{
1520 		// Are we close, if so , peck!
1521 		if ( SpacesAway( pSoldier->sGridNo, sCorpseGridNo ) < 2 )
1522 		{
1523 			// Change facing
1524 			sFacingDir = GetDirectionFromGridNo( sCorpseGridNo, pSoldier );
1525 
1526 			if ( sFacingDir != pSoldier->bDirection )
1527 			{
1528 				pSoldier->usActionData = sFacingDir;
1529 				return(AI_ACTION_CHANGE_FACING);
1530 			}
1531 			else if (!gfTurnBasedAI)
1532 			{
1533 				pSoldier->usActionData = 30000;
1534 				return( AI_ACTION_WAIT );
1535 			}
1536 			else
1537 			{
1538 				return( AI_ACTION_NONE );
1539 			}
1540 		}
1541 		else
1542 		{
1543 			// Walk to nearest one!
1544 			pSoldier->usActionData = FindGridNoFromSweetSpot(pSoldier, sCorpseGridNo, 4);
1545 			if ( pSoldier->usActionData != NOWHERE )
1546 			{
1547 				return( AI_ACTION_GET_CLOSER );
1548 			}
1549 		}
1550 	}
1551 
1552 	return( AI_ACTION_NONE );
1553 }
1554 
CrowDecideAction(SOLDIERTYPE * pSoldier)1555 INT8 CrowDecideAction( SOLDIERTYPE * pSoldier )
1556 {
1557 	if ( pSoldier->usAnimState == CROW_FLY )
1558 	{
1559 		return( AI_ACTION_NONE );
1560 	}
1561 
1562 	switch( pSoldier->bAlertStatus )
1563 	{
1564 		case STATUS_GREEN:
1565 		case STATUS_YELLOW:
1566 			return( CrowDecideActionGreen( pSoldier ) );
1567 
1568 		case STATUS_RED:
1569 		case STATUS_BLACK:
1570 			return( CrowDecideActionRed( pSoldier ) );
1571 
1572 		default:
1573 			Assert( FALSE );
1574 			return( AI_ACTION_NONE );
1575 	}
1576 }
1577