1 #include "AI.h"
2 #include "AIInternals.h"
3 #include "Animation_Control.h"
4 #include "Boxing.h"
5 #include "ContentManager.h"
6 #include "GameInstance.h"
7 #include "GamePolicy.h"
8 #include "Isometric_Utils.h"
9 #include "Items.h"
10 #include "Keys.h"
11 #include "LOS.h"
12 #include "Logger.h"
13 #include "Map_Screen_Interface_Map.h"
14 #include "Message.h"
15 #include "NPC.h"
16 #include "OppList.h"
17 #include "Overhead.h"
18 #include "PathAI.h"
19 #include "Points.h"
20 #include "Quests.h"
21 #include "Render_Fun.h"
22 #include "Rotting_Corpses.h"
23 #include "Scheduling.h"
24 #include "Soldier_Ani.h"
25 #include "Soldier_Functions.h"
26 #include "Soldier_Macros.h"
27 #include "Soldier_Profile.h"
28 #include "StrategicMap.h"
29 #include "Structure.h"
30 #include "Structure_Wrap.h"
31 #include "Timer_Control.h"
32 #include "WeaponModels.h"
33 #include "Weapons.h"
34 #include "WorldMan.h"
35 #include <string_theory/format>
36 
37 extern BOOLEAN gfUseAlternateQueenPosition;
38 
39 // global status time counters to determine what takes the most time
40 
41 #ifdef AI_TIMING_TESTS
42 static UINT32 guiGreenTimeTotal = 0, guiYellowTimeTotal = 0, guiRedTimeTotal = 0, guiBlackTimeTotal = 0;
43 static UINT32 guiGreenCounter = 0, guiYellowCounter = 0, guiRedCounter = 0, guiBlackCounter = 0;
44 static UINT32 guiRedSeekTimeTotal = 0, guiRedHelpTimeTotal = 0, guiRedHideTimeTotal = 0;
45 static UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0;
46 #endif
47 
48 #define CENTER_OF_RING 11237
49 
50 
DoneScheduleAction(SOLDIERTYPE * pSoldier)51 static void DoneScheduleAction(SOLDIERTYPE* pSoldier)
52 {
53 	pSoldier->fAIFlags &= (~AI_CHECK_SCHEDULE);
54 	pSoldier->bAIScheduleProgress = 0;
55 	PostNextSchedule( pSoldier );
56 }
57 
58 
DecideActionSchedule(SOLDIERTYPE * pSoldier)59 static INT8 DecideActionSchedule(SOLDIERTYPE* pSoldier)
60 {
61 	SCHEDULENODE *		pSchedule;
62 	INT32							iScheduleIndex;
63 	UINT8							ubScheduleAction;
64 	UINT16						usGridNo1, usGridNo2;
65 	INT8							bDirection;
66 	STRUCTURE *				pStructure;
67 	BOOLEAN						fDoUseDoor;
68 	DOOR_STATUS	*			pDoorStatus;
69 
70 	pSchedule = GetSchedule( pSoldier->ubScheduleID );
71 	if (!pSchedule)
72 	{
73 		return( AI_ACTION_NONE );
74 	}
75 
76 	if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE1)
77 	{
78 		iScheduleIndex = 0;
79 	}
80 	else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE2)
81 	{
82 		iScheduleIndex = 1;
83 	}
84 	else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE3)
85 	{
86 		iScheduleIndex = 2;
87 	}
88 	else if (pSchedule->usFlags & SCHEDULE_FLAGS_ACTIVE4)
89 	{
90 		iScheduleIndex = 3;
91 	}
92 	else
93 	{
94 		// error!
95 		return( AI_ACTION_NONE );
96 	}
97 
98 	ubScheduleAction = pSchedule->ubAction[ iScheduleIndex ];
99 	usGridNo1 = pSchedule->usData1[ iScheduleIndex ];
100 	usGridNo2 = pSchedule->usData2[ iScheduleIndex ];
101 
102 	// assume soldier is awake unless the action is a sleep
103 	pSoldier->fAIFlags &= ~(AI_ASLEEP);
104 
105 	switch( ubScheduleAction )
106 	{
107 		case SCHEDULE_ACTION_LOCKDOOR:
108 			//Uses first gridno for locking door, then second to move to after door is locked.
109 			//It is possible that the second gridno will border the edge of the map, meaning that
110 			//the individual will walk off of the map.
111 			//If this is a "merchant", make sure that nobody occupies the building/room.
112 
113 			switch( pSoldier->bAIScheduleProgress )
114 			{
115 				case 0: // move to gridno specified
116 					if (pSoldier->sGridNo == usGridNo1)
117 					{
118 						pSoldier->bAIScheduleProgress++;
119 						// fall through
120 					}
121 					else
122 					{
123 						pSoldier->usActionData = usGridNo1;
124 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
125 						return( AI_ACTION_SCHEDULE_MOVE );
126 					}
127 					// fall through
128 				case 1:
129 					// start the door open: find the door...
130 					usGridNo1 = FindDoorAtGridNoOrAdjacent( usGridNo1 );
131 
132 					if ( usGridNo1 == NOWHERE )
133 					{
134 						// do nothing right now!
135 						return( AI_ACTION_NONE );
136 					}
137 
138 					pDoorStatus = GetDoorStatus( usGridNo1 );
139 					if (pDoorStatus && pDoorStatus->ubFlags & DOOR_BUSY)
140 					{
141 						// do nothing right now!
142 						return( AI_ACTION_NONE );
143 					}
144 
145 					pStructure = FindStructure( usGridNo1, STRUCTURE_ANYDOOR );
146 					if (pStructure == NULL)
147 					{
148 						fDoUseDoor = FALSE;
149 					}
150 					else
151 					{
152 						// action-specific tests to not handle the door
153 						fDoUseDoor = TRUE;
154 
155 						if (pStructure->fFlags & STRUCTURE_OPEN)
156 						{
157 							// not only do we have to lock the door but
158 							// close it too!
159 							pSoldier->fAIFlags |= AI_LOCK_DOOR_INCLUDES_CLOSE;
160 						}
161 						else
162 						{
163 							DOOR * pDoor;
164 
165 							pDoor = FindDoorInfoAtGridNo( usGridNo1 );
166 							if (pDoor)
167 							{
168 								if (pDoor->fLocked)
169 								{
170 									// door already locked!
171 									fDoUseDoor = FALSE;
172 								}
173 								else
174 								{
175 									pDoor->fLocked = TRUE;
176 								}
177 							}
178 							else
179 							{
180 								STLOGW("Schedule involved locked door at {} but there's no lock there!", usGridNo1);
181 								fDoUseDoor = FALSE;
182 							}
183 						}
184 					}
185 
186 					if (fDoUseDoor)
187 					{
188 						pSoldier->usActionData = usGridNo1;
189 						return( AI_ACTION_LOCK_DOOR );
190 					}
191 
192 					// the door is already in the desired state, or it doesn't exist!
193 					pSoldier->bAIScheduleProgress++;
194 					// fall through
195 
196 				case 2:
197 					if (pSoldier->sGridNo == usGridNo2 || pSoldier->sGridNo == NOWHERE)
198 					{
199 						// NOWHERE indicates we were supposed to go off map and have done so
200 						DoneScheduleAction( pSoldier );
201 
202 						if ( pSoldier->sGridNo != NOWHERE )
203 						{
204 							pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
205 						}
206 					}
207 					else
208 					{
209 						if ( GridNoOnEdgeOfMap( usGridNo2, &bDirection ) )
210 						{
211 							// told to go to edge of map, so go off at that point!
212 							pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
213 						}
214 						pSoldier->usActionData = usGridNo2;
215 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
216 						return( AI_ACTION_SCHEDULE_MOVE );
217 					}
218 					break;
219 			}
220 			break;
221 
222 
223 		case SCHEDULE_ACTION_UNLOCKDOOR:
224 		case SCHEDULE_ACTION_OPENDOOR:
225 		case SCHEDULE_ACTION_CLOSEDOOR:
226 			//Uses first gridno for opening/closing/unlocking door, then second to move to after door is opened.
227 			//It is possible that the second gridno will border the edge of the map, meaning that
228 			//the individual will walk off of the map.
229 			switch( pSoldier->bAIScheduleProgress )
230 			{
231 				case 0: // move to gridno specified
232 					if (pSoldier->sGridNo == usGridNo1)
233 					{
234 						pSoldier->bAIScheduleProgress++;
235 						// fall through
236 					}
237 					else
238 					{
239 						pSoldier->usActionData = usGridNo1;
240 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
241 						return( AI_ACTION_SCHEDULE_MOVE );
242 					}
243 					// fall through
244 				case 1:
245 					// start the door open: find the door...
246 					usGridNo1 = FindDoorAtGridNoOrAdjacent( usGridNo1 );
247 
248 					if ( usGridNo1 == NOWHERE )
249 					{
250 						// do nothing right now!
251 						return( AI_ACTION_NONE );
252 					}
253 
254 					pDoorStatus = GetDoorStatus( usGridNo1 );
255 					if (pDoorStatus && pDoorStatus->ubFlags & DOOR_BUSY)
256 					{
257 						// do nothing right now!
258 						return( AI_ACTION_NONE );
259 					}
260 
261 					pStructure = FindStructure( usGridNo1, STRUCTURE_ANYDOOR );
262 					if (pStructure == NULL)
263 					{
264 						fDoUseDoor = FALSE;
265 					}
266 					else
267 					{
268 						fDoUseDoor = TRUE;
269 
270 						// action-specific tests to not handle the door
271 						switch( ubScheduleAction )
272 						{
273 							case SCHEDULE_ACTION_UNLOCKDOOR:
274 								if (pStructure->fFlags & STRUCTURE_OPEN)
275 								{
276 									// door is already open!
277 									fDoUseDoor = FALSE;
278 								}
279 								else
280 								{
281 									// set the door to unlocked
282 									DOOR * pDoor;
283 
284 									pDoor = FindDoorInfoAtGridNo( usGridNo1 );
285 									if (pDoor)
286 									{
287 										if (pDoor->fLocked)
288 										{
289 											pDoor->fLocked = FALSE;
290 										}
291 										else
292 										{
293 											// door already unlocked!
294 											fDoUseDoor = FALSE;
295 										}
296 									}
297 									else
298 									{
299 										// WTF?  Warning time!
300 										STLOGW("Schedule involved locked door at {} but there's no lock there!", usGridNo1);
301 										fDoUseDoor = FALSE;
302 									}
303 								}
304 								break;
305 							case SCHEDULE_ACTION_OPENDOOR:
306 								if (pStructure->fFlags & STRUCTURE_OPEN)
307 								{
308 									// door is already open!
309 									fDoUseDoor = FALSE;
310 								}
311 								break;
312 							case SCHEDULE_ACTION_CLOSEDOOR:
313 								if ( !(pStructure->fFlags & STRUCTURE_OPEN) )
314 								{
315 									// door is already closed!
316 									fDoUseDoor = FALSE;
317 								}
318 								break;
319 							default:
320 								break;
321 						}
322 					}
323 
324 					if (fDoUseDoor)
325 					{
326 						pSoldier->usActionData = usGridNo1;
327 						if (ubScheduleAction == SCHEDULE_ACTION_UNLOCKDOOR)
328 						{
329 							return( AI_ACTION_UNLOCK_DOOR );
330 						}
331 						else
332 						{
333 							return( AI_ACTION_OPEN_OR_CLOSE_DOOR );
334 						}
335 					}
336 
337 					// the door is already in the desired state, or it doesn't exist!
338 					pSoldier->bAIScheduleProgress++;
339 					// fall through
340 
341 				case 2:
342 					if (pSoldier->sGridNo == usGridNo2 || pSoldier->sGridNo == NOWHERE)
343 					{
344 						// NOWHERE indicates we were supposed to go off map and have done so
345 						DoneScheduleAction( pSoldier );
346 						if ( pSoldier->sGridNo != NOWHERE )
347 						{
348 							pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
349 						}
350 					}
351 					else
352 					{
353 						if ( GridNoOnEdgeOfMap( usGridNo2, &bDirection ) )
354 						{
355 							// told to go to edge of map, so go off at that point!
356 							pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
357 						}
358 						pSoldier->usActionData = usGridNo2;
359 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
360 						return( AI_ACTION_SCHEDULE_MOVE );
361 					}
362 					break;
363 			}
364 			break;
365 
366 		case SCHEDULE_ACTION_GRIDNO:
367 			// Only uses the first gridno
368 			if ( pSoldier->sGridNo == usGridNo1 )
369 			{
370 				// done!
371 				DoneScheduleAction( pSoldier );
372 				if ( pSoldier->sGridNo != NOWHERE )
373 				{
374 					pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
375 				}
376 			}
377 			else
378 			{
379 				// move!
380 				pSoldier->usActionData = usGridNo1;
381 				pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
382 				return( AI_ACTION_SCHEDULE_MOVE );
383 			}
384 			break;
385 		case SCHEDULE_ACTION_LEAVESECTOR:
386 			//Doesn't use any gridno data
387 			switch( pSoldier->bAIScheduleProgress )
388 			{
389 				case 0: // start the action
390 
391 					pSoldier->usActionData = FindNearestEdgePoint( pSoldier->sGridNo );
392 
393 					if (pSoldier->usActionData == NOWHERE)
394 					{
395 						SLOGD("Civilian could not find path to map edge!" );
396 						DoneScheduleAction( pSoldier );
397 						return( AI_ACTION_NONE );
398 					}
399 
400 					if ( pSoldier->sGridNo == pSoldier->usActionData )
401 					{
402 						// time to go off the map
403 						pSoldier->bAIScheduleProgress++;
404 					}
405 					else
406 					{
407 						// move!
408 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
409 						return( AI_ACTION_SCHEDULE_MOVE );
410 					}
411 
412 					// fall through
413 
414 				case 1: // near edge
415 
416 					pSoldier->usActionData = FindNearbyPointOnEdgeOfMap( pSoldier, &bDirection );
417 					if (pSoldier->usActionData == NOWHERE)
418 					{
419 						// what the heck??
420 						// ABORT!
421 						DoneScheduleAction( pSoldier );
422 					}
423 					else
424 					{
425 						pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bDirection );
426 						pSoldier->bAIScheduleProgress++;
427 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
428 						return( AI_ACTION_SCHEDULE_MOVE );
429 					}
430 					break;
431 
432 				case 2: // should now be done!
433 					DoneScheduleAction( pSoldier );
434 					break;
435 
436 				default:
437 					break;
438 			}
439 			break;
440 
441 		case SCHEDULE_ACTION_ENTERSECTOR:
442 			if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags & PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR )
443 			{
444 				// ignore.
445 				DoneScheduleAction( pSoldier );
446 				break;
447 			}
448 			switch( pSoldier->bAIScheduleProgress )
449 			{
450 				case 0:
451 					EVENT_SetSoldierPosition(pSoldier, pSoldier->sOffWorldGridNo, SSP_NONE);
452 					pSoldier->bInSector = TRUE;
453 					MoveSoldierFromAwayToMercSlot( pSoldier );
454 					pSoldier->usActionData = usGridNo1;
455 					pSoldier->bAIScheduleProgress++;
456 					pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
457 					return( AI_ACTION_SCHEDULE_MOVE );
458 				case 1:
459 					if (pSoldier->sGridNo == usGridNo1)
460 					{
461 						DoneScheduleAction( pSoldier );
462 						if ( pSoldier->sGridNo != NOWHERE )
463 						{
464 							pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
465 						}
466 					}
467 					else
468 					{
469 						pSoldier->usActionData = usGridNo1;
470 						pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
471 						return( AI_ACTION_SCHEDULE_MOVE );
472 					}
473 					break;
474 			}
475 			break;
476 
477 		case SCHEDULE_ACTION_WAKE:
478 			// Go to this position
479 			if (pSoldier->sGridNo == pSoldier->sInitialGridNo)
480 			{
481 				// th-th-th-that's it!
482 				DoneScheduleAction( pSoldier );
483 				pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
484 			}
485 			else
486 			{
487 				pSoldier->usActionData = pSoldier->sInitialGridNo;
488 				pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
489 				return( AI_ACTION_SCHEDULE_MOVE );
490 			}
491 			break;
492 
493 		case SCHEDULE_ACTION_SLEEP:
494 			// Go to this position
495 			if (pSoldier->sGridNo == usGridNo1)
496 			{
497 				// Sleep
498 				pSoldier->fAIFlags |= AI_ASLEEP;
499 				DoneScheduleAction( pSoldier );
500 				if ( pSoldier->sGridNo != NOWHERE )
501 				{
502 					pSoldier->usPatrolGrid[0] = pSoldier->sGridNo;
503 				}
504 			}
505 			else
506 			{
507 				pSoldier->usActionData = usGridNo1;
508 				pSoldier->sAbsoluteFinalDestination = pSoldier->usActionData;
509 				return( AI_ACTION_SCHEDULE_MOVE );
510 			}
511 			break;
512 	}
513 
514 
515 	return( AI_ACTION_NONE );
516 }
517 
518 
DecideActionBoxerEnteringRing(SOLDIERTYPE * pSoldier)519 static INT8 DecideActionBoxerEnteringRing(SOLDIERTYPE* pSoldier)
520 {
521 	INT16	sDesiredMercLoc;
522 
523 	// boxer, should move into ring!
524 	UINT8 const room = GetRoom(pSoldier->sGridNo);
525 	if (room == BOXING_RING)
526 	{
527 		// look towards nearest player
528 		sDesiredMercLoc = ClosestPC( pSoldier, NULL );
529 		if ( sDesiredMercLoc != NOWHERE )
530 		{
531 			// see if we are facing this person
532 			const UINT8 ubDesiredMercDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sDesiredMercLoc);
533 
534 			// if not already facing in that direction,
535 			if ( pSoldier->bDirection != ubDesiredMercDir && InternalIsValidStance( pSoldier, ubDesiredMercDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
536 			{
537 				pSoldier->usActionData = ubDesiredMercDir;
538 				return( AI_ACTION_CHANGE_FACING );
539 			}
540 		}
541 		return( AI_ACTION_ABSOLUTELY_NONE );
542 	}
543 	else if (room != NO_ROOM)
544 	{
545 		// move to starting spot
546 		pSoldier->usActionData = FindClosestBoxingRingSpot( pSoldier, TRUE );
547 		return( AI_ACTION_GET_CLOSER );
548 	}
549 
550 	return( AI_ACTION_ABSOLUTELY_NONE );
551 }
552 
553 
DecideActionNamedNPC(SOLDIERTYPE * pSoldier)554 static INT8 DecideActionNamedNPC(SOLDIERTYPE* pSoldier)
555 {
556 	INT16 sDesiredMercLoc;
557 	INT16	sDesiredMercDist;
558 
559 	// if a quote record has been set and we're not doing movement, then
560 	// it means we have to wait until someone is nearby and then see
561 	// to do...
562 
563 	// is this person close enough to trigger event?
564 	if (pSoldier->ubQuoteRecord && pSoldier->ubQuoteActionID == QUOTE_ACTION_ID_TURNTOWARDSPLAYER )
565 	{
566 		sDesiredMercLoc = ClosestPC( pSoldier, &sDesiredMercDist );
567 		if (sDesiredMercLoc != NOWHERE )
568 		{
569 			if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2)
570 			{
571 				pSoldier->ubQuoteRecord = 0;
572 				// see if this triggers a conversation/NPC record
573 				PCsNearNPC( pSoldier->ubProfile );
574 				// clear "handle every frame" flag
575 				pSoldier->fAIFlags &= (~AI_HANDLE_EVERY_FRAME);
576 				return( AI_ACTION_ABSOLUTELY_NONE );
577 			}
578 
579 			// see if we are facing this person
580 			const UINT8 ubDesiredMercDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sDesiredMercLoc);
581 
582 			// if not already facing in that direction,
583 			if (pSoldier->bDirection != ubDesiredMercDir && InternalIsValidStance( pSoldier, ubDesiredMercDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
584 			{
585 				pSoldier->usActionData = ubDesiredMercDir;
586 				return( AI_ACTION_CHANGE_FACING );
587 			}
588 		}
589 
590 		// do nothing; we're looking at the PC or the NPC is far away
591 		return( AI_ACTION_ABSOLUTELY_NONE );
592 
593 	}
594 	else
595 	{
596 		///////////////
597 		// CHECK TO SEE IF WE WANT TO GO UP TO PERSON AND SAY SOMETHING
598 		///////////////
599 		pSoldier->usActionData = NPCConsiderInitiatingConv(pSoldier);
600 		if (pSoldier->usActionData != NOWHERE)
601 		{
602 			return( AI_ACTION_APPROACH_MERC );
603 		}
604 	}
605 
606 	switch( pSoldier->ubProfile )
607 	{
608 		case JIM:
609 		case JACK:
610 		case OLAF:
611 		case RAY:
612 		case OLGA:
613 		case TYRONE:
614 			sDesiredMercLoc = ClosestPC( pSoldier, &sDesiredMercDist );
615 			if (sDesiredMercLoc != NOWHERE )
616 			{
617 				if ( sDesiredMercDist <= NPC_TALK_RADIUS * 2 )
618 				{
619 					AddToShouldBecomeHostileOrSayQuoteList(pSoldier);
620 					// now wait a bit!
621 					pSoldier->usActionData = 5000;
622 					return( AI_ACTION_WAIT );
623 				}
624 				else
625 				{
626 					pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, sDesiredMercLoc, AI_ACTION_APPROACH_MERC );
627 					if ( pSoldier->usActionData != NOWHERE )
628 					{
629 						return( AI_ACTION_APPROACH_MERC );
630 					}
631 				}
632 			}
633 			break;
634 		default:
635 			break;
636 	}
637 
638 	return( AI_ACTION_NONE );
639 }
640 
641 
DecideActionGreen(SOLDIERTYPE * pSoldier)642 static INT8 DecideActionGreen(SOLDIERTYPE* pSoldier)
643 {
644 	INT32 iChance, iSneaky = 10;
645 	INT8  bInWater,bInGas;
646 
647 	const BOOLEAN fCivilian =
648 		IsOnCivTeam(pSoldier) &&
649 		(
650 			pSoldier->ubCivilianGroup == NON_CIV_GROUP ||
651 			pSoldier->bNeutral                         ||
652 			(FATCIV <= pSoldier->ubBodyType && pSoldier->ubBodyType <= CRIPPLECIV)
653 		);
654 	BOOLEAN fCivilianOrMilitia = PTR_CIV_OR_MILITIA;
655 
656 	gubNPCPathCount = 0;
657 
658 	if ( gTacticalStatus.bBoxingState != NOT_BOXING )
659 	{
660 		if (pSoldier->uiStatusFlags & SOLDIER_BOXER)
661 		{
662 			if ( gTacticalStatus.bBoxingState == PRE_BOXING )
663 			{
664 				return( DecideActionBoxerEnteringRing( pSoldier ) );
665 			}
666 			else
667 			{
668 				UINT8 ubLoop;
669 
670 				// boxer... but since in status green, it's time to leave the ring!
671 				UINT8 const room = GetRoom(pSoldier->sGridNo);
672 				if (room == BOXING_RING)
673 				{
674 					for ( ubLoop = 0; ubLoop < NUM_BOXERS; ubLoop++ )
675 					{
676 						if (pSoldier == gBoxer[ubLoop])
677 						{
678 							// we should go back where we started
679 							pSoldier->usActionData = gsBoxerGridNo[ ubLoop ];
680 							return( AI_ACTION_GET_CLOSER );
681 						}
682 					}
683 					pSoldier->usActionData = FindClosestBoxingRingSpot( pSoldier, FALSE );
684 					return( AI_ACTION_GET_CLOSER );
685 				}
686 				else if (room != NO_ROOM)
687 				{
688 					// done!
689 					pSoldier->uiStatusFlags &= ~(SOLDIER_BOXER);
690 					if (pSoldier->bTeam == OUR_TEAM)
691 					{
692 						pSoldier->uiStatusFlags &= (~SOLDIER_PCUNDERAICONTROL);
693 						TriggerEndOfBoxingRecord( pSoldier );
694 					}
695 					else if ( CountPeopleInBoxingRing() == 0 && (gTacticalStatus.bBoxingState == DISQUALIFIED))
696 					{
697 						// Probably disqualified by jumping out of ring; the player
698 						// character then didn't trigger the end of boxing record
699 						// (and we know from the if statement above that we're
700 						// still in a boxing state of some sort...)
701 						TriggerEndOfBoxingRecord( NULL );
702 
703 					}
704 				}
705 
706 				return( AI_ACTION_ABSOLUTELY_NONE );
707 			}
708 		}
709 		//else if ( (gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING) && ( PythSpacesAway( pSoldier->sGridNo, CENTER_OF_RING ) <= MaxDistanceVisible() ) )
710 		else if ( PythSpacesAway( pSoldier->sGridNo, CENTER_OF_RING ) <= MaxDistanceVisible() )
711 		{
712 			// face ring!
713 			const UINT8 ubRingDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, CENTER_OF_RING);
714 			if ( gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
715 			{
716 				if ( pSoldier->bDirection != ubRingDir )
717 				{
718 					pSoldier->usActionData = ubRingDir;
719 					return( AI_ACTION_CHANGE_FACING );
720 				}
721 			}
722 			return( AI_ACTION_NONE );
723 		}
724 	}
725 
726 	if ( TANK( pSoldier ) )
727 	{
728 		return( AI_ACTION_NONE );
729 	}
730 
731 
732 	bInWater = Water( pSoldier->sGridNo );
733 
734 	// check if standing in tear gas without a gas mask on, or in smoke
735 	bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
736 
737 	// if real-time, and not in the way, do nothing 90% of the time (for GUARDS!)
738 	// unless in water (could've started there), then we better swim to shore!
739 
740 	if (fCivilian)
741 	{
742 		// special stuff for civs
743 
744 		if (pSoldier->uiStatusFlags & SOLDIER_COWERING)
745 		{
746 			// everything's peaceful again, stop cowering!!
747 			pSoldier->usActionData = ANIM_STAND;
748 			return( AI_ACTION_STOP_COWERING );
749 		}
750 
751 		if (!gfTurnBasedAI)
752 		{
753 			// ******************
754 			// REAL TIME NPC CODE
755 			// ******************
756 			if (pSoldier->fAIFlags & AI_CHECK_SCHEDULE)
757 			{
758 				pSoldier->bAction = DecideActionSchedule( pSoldier );
759 				if (pSoldier->bAction != AI_ACTION_NONE)
760 				{
761 					return( pSoldier->bAction );
762 				}
763 			}
764 
765 			if (pSoldier->ubProfile != NO_PROFILE)
766 			{
767 				pSoldier->bAction = DecideActionNamedNPC( pSoldier );
768 				if ( pSoldier->bAction != AI_ACTION_NONE )
769 				{
770 					return( pSoldier->bAction );
771 				}
772 				// can we act again? not for a minute since we were last spoken to/triggered a record
773 				if ( pSoldier->uiTimeSinceLastSpoke && (GetJA2Clock() < pSoldier->uiTimeSinceLastSpoke + 60000) )
774 				{
775 					return( AI_ACTION_NONE );
776 				}
777 				// turn off counter so we don't check it again
778 				pSoldier->uiTimeSinceLastSpoke = 0;
779 
780 			}
781 
782 		}
783 
784 		// if not in the way, do nothing most of the time
785 		// unless in water (could've started there), then we better swim to shore!
786 
787 		if (!(bInWater) && PreRandom( 5 ) )
788 		{
789 			// don't do nuttin!
790 			return( AI_ACTION_NONE );
791 		}
792 
793 	}
794 
795 	////////////////////////////////////////////////////////////////////////////
796 	// POINT PATROL: move towards next point unless getting a bit winded
797 	////////////////////////////////////////////////////////////////////////////
798 
799 	// this takes priority over water/gas checks, so that point patrol WILL work
800 	// from island to island, and through gas covered areas, too
801 	if ((pSoldier->bOrders == POINTPATROL) && (pSoldier->bBreath >= 75))
802 	{
803 		if (PointPatrolAI(pSoldier))
804 		{
805 			if (!gfTurnBasedAI)
806 			{
807 				// wait after this...
808 				pSoldier->bNextAction = AI_ACTION_WAIT;
809 				pSoldier->usNextActionData = RealtimeDelay( pSoldier );
810 			}
811 			return(AI_ACTION_POINT_PATROL);
812 		}
813 		else
814 		{
815 			// Reset path count to avoid dedlok
816 			gubNPCPathCount = 0;
817 		}
818 	}
819 
820 	if ((pSoldier->bOrders == RNDPTPATROL) && (pSoldier->bBreath >=75))
821 	{
822 		if (RandomPointPatrolAI(pSoldier))
823 		{
824 			if (!gfTurnBasedAI)
825 			{
826 				// wait after this...
827 				pSoldier->bNextAction = AI_ACTION_WAIT;
828 				pSoldier->usNextActionData = RealtimeDelay( pSoldier );
829 			}
830 			return(AI_ACTION_POINT_PATROL);
831 		}
832 		else
833 		{
834 			// Reset path count to avoid dedlok
835 			gubNPCPathCount = 0;
836 		}
837 
838 	}
839 
840 	////////////////////////////////////////////////////////////////////////////
841 	// WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
842 	////////////////////////////////////////////////////////////////////////////
843 
844 
845 	if (bInWater || bInGas)
846 	{
847 		pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
848 		if (pSoldier->usActionData != NOWHERE)
849 		{
850 			return(AI_ACTION_LEAVE_WATER_GAS);
851 		}
852 	}
853 
854 
855 	////////////////////////////////////////////////////////////////////////
856 	// REST IF RUNNING OUT OF BREATH
857 	////////////////////////////////////////////////////////////////////////
858 
859 	// if our breath is running a bit low, and we're not in the way or in water
860 	if ((pSoldier->bBreath < 75) && !bInWater)
861 	{
862 		// take a breather for gods sake!
863 		// for realtime, AI will use a standard wait set outside of here
864 		pSoldier->usActionData = NOWHERE;
865 		return(AI_ACTION_NONE);
866 	}
867 
868 
869 	////////////////////////////////////////////////////////////////////////////
870 	// RANDOM PATROL:  determine % chance to start a new patrol route
871 	////////////////////////////////////////////////////////////////////////////
872 
873 	if (!gubNPCPathCount) // try to limit pathing in Green AI
874 	{
875 
876 		iChance = 25 + pSoldier->bBypassToGreen;
877 
878 		// set base chance according to orders
879 		switch (pSoldier->bOrders)
880 		{
881 			case STATIONARY:     iChance += -20;  break;
882 			case ONGUARD:        iChance += -15;  break;
883 			case ONCALL:                          break;
884 			case CLOSEPATROL:    iChance += +15;  break;
885 			case RNDPTPATROL:
886 			case POINTPATROL:    iChance = 0;     break;
887 				/*
888 				if ( !gfTurnBasedAI )
889 				{
890 					// realtime deadlock... increase chance!
891 					iChance = 110;// more than 100 in case person is defensive
892 				}
893 				else if ( pSoldier->bInitialActionPoints < pSoldier->bActionPoints ) // could be less because of carried-over points
894 				{
895 					// CJC: allow pt patrol guys to do a random move in case
896 					// of a deadlock provided they haven't done anything yet this turn
897 					iChance=   0;
898 				}
899 				break;
900 				*/
901 			case FARPATROL:      iChance += +25;  break;
902 			case SEEKENEMY:      iChance += -10;  break;
903 		}
904 
905 		// modify chance of patrol (and whether it's a sneaky one) by attitude
906 		switch (pSoldier->bAttitude)
907 		{
908 			case DEFENSIVE:      iChance += -10;                 break;
909 			case BRAVESOLO:      iChance +=   5;                 break;
910 			case BRAVEAID:                                       break;
911 			case CUNNINGSOLO:    iChance +=   5;  iSneaky += 10; break;
912 			case CUNNINGAID:                      iSneaky +=  5; break;
913 			case AGGRESSIVE:     iChance +=  10;  iSneaky += -5; break;
914 			case ATTACKSLAYONLY: iChance +=  10;  iSneaky += -5; break;
915 		}
916 
917 		// reduce chance for any injury, less likely to wander around when hurt
918 		iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
919 
920 		// reduce chance if breath is down, less likely to wander around when tired
921 		iChance -= (100 - pSoldier->bBreath);
922 
923 
924 		// if we're in water with land miles (> 25 tiles) away,
925 		// OR if we roll under the chance calculated
926 		if (bInWater || ((INT16) PreRandom(100) < iChance))
927 		{
928 			pSoldier->usActionData = RandDestWithinRange(pSoldier);
929 
930 			if (pSoldier->usActionData != NOWHERE)
931 			{
932 				pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, AI_ACTION_RANDOM_PATROL );
933 			}
934 
935 			if (pSoldier->usActionData != NOWHERE)
936 			{
937 				if (!gfTurnBasedAI)
938 				{
939 					// wait after this...
940 					pSoldier->bNextAction = AI_ACTION_WAIT;
941 					pSoldier->usNextActionData = RealtimeDelay( pSoldier );
942 				}
943 				return(AI_ACTION_RANDOM_PATROL);
944 			}
945 		}
946 	}
947 
948 	if (!gubNPCPathCount) // try to limit pathing in Green AI
949 	{
950 		////////////////////////////////////////////////////////////////////////////
951 		// SEEK FRIEND: determine %chance for man to pay a friendly visit
952 		////////////////////////////////////////////////////////////////////////////
953 
954 		iChance = 25 + pSoldier->bBypassToGreen;
955 
956 		// set base chance and maximum seeking distance according to orders
957 		switch (pSoldier->bOrders)
958 		{
959 			case STATIONARY:     iChance += -20; break;
960 			case ONGUARD:        iChance += -15; break;
961 			case ONCALL:                         break;
962 			case CLOSEPATROL:    iChance += +10; break;
963 			case RNDPTPATROL:
964 			case POINTPATROL:    iChance  = -10; break;
965 			case FARPATROL:      iChance += +20; break;
966 			case SEEKENEMY:      iChance += -10; break;
967 		}
968 
969 		// modify for attitude
970 		switch (pSoldier->bAttitude)
971 		{
972 			case DEFENSIVE:                       break;
973 			case BRAVESOLO:      iChance /= 2;    break;  // loners
974 			case BRAVEAID:       iChance += 10;   break;  // friendly
975 			case CUNNINGSOLO:    iChance /= 2;    break;  // loners
976 			case CUNNINGAID:     iChance += 10;   break;  // friendly
977 			case AGGRESSIVE:                      break;
978 			case ATTACKSLAYONLY:                  break;
979 		}
980 
981 		// reduce chance for any injury, less likely to wander around when hurt
982 		iChance -= (pSoldier->bLifeMax - pSoldier->bLife);
983 
984 		// reduce chance if breath is down
985 		iChance -= (100 - pSoldier->bBreath);         // very likely to wait when exhausted
986 
987 
988 		if ((INT16) PreRandom(100) < iChance)
989 		{
990 			if (RandomFriendWithin(pSoldier))
991 			{
992 				if ( pSoldier->usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, AI_ACTION_SEEK_FRIEND ) )
993 				{
994 					if (fCivilianOrMilitia && !gfTurnBasedAI)
995 					{
996 						// pause at the end of the walk!
997 						pSoldier->bNextAction = AI_ACTION_WAIT;
998 						pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
999 					}
1000 
1001 					return(AI_ACTION_SEEK_FRIEND);
1002 				}
1003 			}
1004 		}
1005 	}
1006 
1007 	////////////////////////////////////////////////////////////////////////////
1008 	// LOOK AROUND: determine %chance for man to turn in place
1009 	////////////////////////////////////////////////////////////////////////////
1010 
1011 	if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
1012 	{
1013 		// avoid 2 consecutive random turns in a row
1014 		if (pSoldier->bLastAction != AI_ACTION_CHANGE_FACING)
1015 		{
1016 			iChance = 25 + pSoldier->bBypassToGreen;
1017 
1018 			// set base chance according to orders
1019 			if (pSoldier->bOrders == STATIONARY)
1020 				iChance += 25;
1021 
1022 			if (pSoldier->bOrders == ONGUARD)
1023 				iChance += 20;
1024 
1025 			if (pSoldier->bAttitude == DEFENSIVE)
1026 				iChance += 25;
1027 
1028 
1029 			if ((INT16)PreRandom(100) < iChance)
1030 			{
1031 				// roll random directions (stored in actionData) until different from current
1032 				do
1033 				{
1034 					// if man has a LEGAL dominant facing, and isn't facing it, he will turn
1035 					// back towards that facing 50% of the time here (normally just enemies)
1036 					if ((pSoldier->bDominantDir >= 0) && (pSoldier->bDominantDir <= 8) &&
1037 						(pSoldier->bDirection != pSoldier->bDominantDir) && PreRandom(2))
1038 					{
1039 						pSoldier->usActionData = pSoldier->bDominantDir;
1040 					}
1041 					else
1042 					{
1043 						pSoldier->usActionData = (UINT16)PreRandom(8);
1044 					}
1045 				}
1046 				while (pSoldier->usActionData == pSoldier->bDirection);
1047 
1048 				if ( InternalIsValidStance( pSoldier, (INT8) pSoldier->usActionData, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
1049 				{
1050 
1051 					if (!gfTurnBasedAI)
1052 					{
1053 						// wait after this...
1054 						pSoldier->bNextAction = AI_ACTION_WAIT;
1055 						pSoldier->usNextActionData = RealtimeDelay( pSoldier );
1056 					}
1057 
1058 					return(AI_ACTION_CHANGE_FACING);
1059 				}
1060 			}
1061 		}
1062 	}
1063 
1064 
1065 	////////////////////////////////////////////////////////////////////////////
1066 	// NONE:
1067 	////////////////////////////////////////////////////////////////////////////
1068 
1069 	// by default, if everything else fails, just stands in place without turning
1070 	// for realtime, regular AI guys will use a standard wait set outside of here
1071 	pSoldier->usActionData = NOWHERE;
1072 	return(AI_ACTION_NONE);
1073 }
1074 
1075 
DecideActionYellow(SOLDIERTYPE * pSoldier)1076 static INT8 DecideActionYellow(SOLDIERTYPE* pSoldier)
1077 {
1078 	INT32 iDummy;
1079 	INT16 sNoiseGridNo;
1080 	INT32 iNoiseValue;
1081 	INT32 iChance, iSneaky;
1082 	INT16 sClosestFriend;
1083 	const BOOLEAN fCivilian =
1084 		IsOnCivTeam(pSoldier) &&
1085 		(
1086 			pSoldier->ubCivilianGroup == NON_CIV_GROUP ||
1087 			pSoldier->bNeutral                         ||
1088 			(FATCIV <= pSoldier->ubBodyType && pSoldier->ubBodyType <= CRIPPLECIV)
1089 		);
1090 	BOOLEAN fClimb;
1091 	BOOLEAN fReachable;
1092 
1093 	if (fCivilian)
1094 	{
1095 		if (pSoldier->uiStatusFlags & SOLDIER_COWERING)
1096 		{
1097 			// everything's peaceful again, stop cowering!!
1098 			pSoldier->usActionData = ANIM_STAND;
1099 			return( AI_ACTION_STOP_COWERING );
1100 		}
1101 		if (!gfTurnBasedAI)
1102 		{
1103 			// ******************
1104 			// REAL TIME NPC CODE
1105 			// ******************
1106 			if (pSoldier->ubProfile != NO_PROFILE)
1107 			{
1108 				pSoldier->bAction = DecideActionNamedNPC( pSoldier );
1109 				if ( pSoldier->bAction != AI_ACTION_NONE )
1110 				{
1111 					return( pSoldier->bAction );
1112 				}
1113 
1114 			}
1115 
1116 		}
1117 
1118 	}
1119 
1120 	// determine the most important noise heard, and its relative value
1121 	sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable);
1122 
1123 	if (sNoiseGridNo == NOWHERE)
1124 	{
1125 		// then we have no business being under YELLOW status any more!
1126 		return(AI_ACTION_NONE);
1127 	}
1128 
1129 
1130 
1131 	////////////////////////////////////////////////////////////////////////////
1132 	// LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise
1133 	////////////////////////////////////////////////////////////////////////////
1134 
1135 	// determine direction from this soldier in which the noise lies
1136 	const UINT8 ubNoiseDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sNoiseGridNo);
1137 
1138 	// if soldier is not already facing in that direction,
1139 	// and the noise source is close enough that it could possibly be seen
1140 	if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
1141 	{
1142 		if ((pSoldier->bDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= MaxDistanceVisible() )
1143 		{
1144 			// set base chance according to orders
1145 			if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
1146 				iChance = 60;
1147 			else           // all other orders
1148 				iChance = 35;
1149 
1150 			if (pSoldier->bAttitude == DEFENSIVE)
1151 				iChance += 15;
1152 
1153 
1154 			if ((INT16)PreRandom(100) < iChance && InternalIsValidStance( pSoldier, ubNoiseDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
1155 			{
1156 				pSoldier->usActionData = ubNoiseDir;
1157 				return(AI_ACTION_CHANGE_FACING);
1158 			}
1159 		}
1160 	}
1161 
1162 
1163 	////////////////////////////////////////////////////////////////////////////
1164 	// RADIO YELLOW ALERT: determine %chance to call others and report noise
1165 	////////////////////////////////////////////////////////////////////////////
1166 
1167 	// if we have the action points remaining to RADIO
1168 	// (we never want NPCs to choose to radio if they would have to wait a turn)
1169 	if ( !fCivilian && (pSoldier->bActionPoints >= AP_RADIO) &&
1170 		(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
1171 	{
1172 		// base chance depends on how much new info we have to radio to the others
1173 		iChance = 5 * WhatIKnowThatPublicDont(pSoldier,FALSE);   // use 5 * for YELLOW alert
1174 
1175 		// if I actually know something they don't and I ain't swimming (deep water)
1176 		if (iChance && !DeepWater( pSoldier->sGridNo ))
1177 		{
1178 
1179 			// CJC: this addition allows for varying difficulty levels for soldier types
1180 			iChance += gbDiff[ DIFF_RADIO_RED_ALERT ][ SoldierDifficultyLevel( pSoldier ) ] / 2;
1181 
1182 			// modify base chance according to orders
1183 			switch (pSoldier->bOrders)
1184 			{
1185 				case STATIONARY: iChance +=  20;  break;
1186 				case ONGUARD:    iChance +=  15;  break;
1187 				case ONCALL:     iChance +=  10;  break;
1188 				case CLOSEPATROL:                 break;
1189 				case RNDPTPATROL:
1190 				case POINTPATROL:                 break;
1191 				case FARPATROL:  iChance += -10;  break;
1192 				case SEEKENEMY:  iChance += -20;  break;
1193 			}
1194 
1195 			// modify base chance according to attitude
1196 			switch (pSoldier->bAttitude)
1197 			{
1198 				case DEFENSIVE:  iChance +=  20;  break;
1199 				case BRAVESOLO:  iChance += -10;  break;
1200 				case BRAVEAID:                    break;
1201 				case CUNNINGSOLO:iChance +=  -5;  break;
1202 				case CUNNINGAID:                  break;
1203 				case AGGRESSIVE: iChance += -20;  break;
1204 				case ATTACKSLAYONLY: iChance = 0; break;
1205 			}
1206 
1207 			if ((INT16)PreRandom(100) < iChance)
1208 			{
1209 				return(AI_ACTION_YELLOW_ALERT);
1210 			}
1211 		}
1212 	}
1213 
1214 	if ( TANK( pSoldier ) )
1215 	{
1216 		return( AI_ACTION_NONE );
1217 	}
1218 
1219 	////////////////////////////////////////////////////////////////////////
1220 	// REST IF RUNNING OUT OF BREATH
1221 	////////////////////////////////////////////////////////////////////////
1222 
1223 	// if our breath is running a bit low, and we're not in water
1224 	if ((pSoldier->bBreath < 25) && !MercInWater(pSoldier))
1225 	{
1226 		// take a breather for gods sake!
1227 		pSoldier->usActionData = NOWHERE;
1228 		return(AI_ACTION_NONE);
1229 	}
1230 
1231 	if ( !( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->ubProfile != ELDIN ) )
1232 	{
1233 		// IF WE ARE MILITIA/CIV IN REALTIME, CLOSE TO NOISE, AND CAN SEE THE SPOT WHERE THE NOISE CAME FROM, FORGET IT
1234 		if ( fReachable && !fClimb && !gfTurnBasedAI && (pSoldier->bTeam == MILITIA_TEAM || pSoldier->bTeam == CIV_TEAM )&& PythSpacesAway( pSoldier->sGridNo, sNoiseGridNo ) < 5 )
1235 		{
1236 			if ( SoldierTo3DLocationLineOfSightTest( pSoldier, sNoiseGridNo, pSoldier->bLevel, 0, 6, TRUE )	)
1237 			{
1238 				// set reachable to false so we don't investigate
1239 				fReachable = FALSE;
1240 				// forget about noise
1241 				pSoldier->sNoiseGridno = NOWHERE;
1242 				pSoldier->ubNoiseVolume = 0;
1243 			}
1244 		}
1245 
1246 		////////////////////////////////////////////////////////////////////////////
1247 		// SEEK NOISE
1248 		////////////////////////////////////////////////////////////////////////////
1249 
1250 		if ( fReachable )
1251 		{
1252 			// remember that noise value is negative, and closer to 0 => more important!
1253 			iChance = 95 + (iNoiseValue / 3);
1254 			iSneaky = 30;
1255 
1256 			// increase
1257 
1258 			// set base chance according to orders
1259 			switch (pSoldier->bOrders)
1260 			{
1261 				case STATIONARY:     iChance += -20;  break;
1262 				case ONGUARD:        iChance += -15;  break;
1263 				case ONCALL:                          break;
1264 				case CLOSEPATROL:    iChance += -10;  break;
1265 				case RNDPTPATROL:
1266 				case POINTPATROL:                     break;
1267 				case FARPATROL:      iChance +=  10;  break;
1268 				case SEEKENEMY:      iChance +=  25;  break;
1269 			}
1270 
1271 			// modify chance of patrol (and whether it's a sneaky one) by attitude
1272 			switch (pSoldier->bAttitude)
1273 			{
1274 				case DEFENSIVE:      iChance += -10;  iSneaky +=  15;  break;
1275 				case BRAVESOLO:      iChance +=  10;                   break;
1276 				case BRAVEAID:       iChance +=   5;                   break;
1277 				case CUNNINGSOLO:    iChance +=   5;  iSneaky +=  30;  break;
1278 				case CUNNINGAID:                      iSneaky +=  30;  break;
1279 				case AGGRESSIVE:     iChance +=  20;  iSneaky += -10;  break;
1280 				case ATTACKSLAYONLY:	iChance +=  20;  iSneaky += -10;  break;
1281 			}
1282 
1283 
1284 			// reduce chance if breath is down, less likely to wander around when tired
1285 			iChance -= (100 - pSoldier->bBreath);
1286 
1287 
1288 			if ((INT16) PreRandom(100) < iChance)
1289 			{
1290 				pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sNoiseGridNo,AI_ACTION_SEEK_NOISE);
1291 
1292 				if (pSoldier->usActionData != NOWHERE)
1293 				{
1294 					if (fClimb && pSoldier->usActionData == sNoiseGridNo)
1295 					{
1296 						// need to climb AND have enough APs to get there this turn
1297 						return( AI_ACTION_MOVE_TO_CLIMB );
1298 					}
1299 
1300 					return(AI_ACTION_SEEK_NOISE);
1301 				}
1302 			}
1303 		}
1304 
1305 
1306 		////////////////////////////////////////////////////////////////////////////
1307 		// SEEK FRIEND WHO LAST RADIOED IN TO REPORT NOISE
1308 		////////////////////////////////////////////////////////////////////////////
1309 
1310 		sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb);
1311 
1312 		// if there is a friend alive & reachable who last radioed in
1313 		if (sClosestFriend != NOWHERE)
1314 		{
1315 			// there a chance enemy soldier choose to go "help" his friend
1316 			iChance = 50 - SpacesAway(pSoldier->sGridNo,sClosestFriend);
1317 			iSneaky = 10;
1318 
1319 			// set base chance according to orders
1320 			switch (pSoldier->bOrders)
1321 			{
1322 				case STATIONARY:     iChance += -20;  break;
1323 				case ONGUARD:        iChance += -15;  break;
1324 				case ONCALL:         iChance +=  20;  break;
1325 				case CLOSEPATROL:    iChance += -10;  break;
1326 				case RNDPTPATROL:
1327 				case POINTPATROL:    iChance += -10;  break;
1328 				case FARPATROL:                       break;
1329 				case SEEKENEMY:      iChance +=  10;  break;
1330 			}
1331 
1332 			// modify chance of patrol (and whether it's a sneaky one) by attitude
1333 			switch (pSoldier->bAttitude)
1334 			{
1335 				case DEFENSIVE:      iChance += -10;  iSneaky +=  15;        break;
1336 				case BRAVESOLO:                                              break;
1337 				case BRAVEAID:       iChance +=  20;  iSneaky += -10;        break;
1338 				case CUNNINGSOLO:                     iSneaky +=  30;        break;
1339 				case CUNNINGAID:     iChance +=  20;  iSneaky +=  20;        break;
1340 				case AGGRESSIVE:     iChance += -20;  iSneaky += -20;        break;
1341 				case ATTACKSLAYONLY: iChance += -20;  iSneaky += -20;        break;
1342 			}
1343 
1344 			// reduce chance if breath is down, less likely to wander around when tired
1345 			iChance -= (100 - pSoldier->bBreath);
1346 
1347 			if ((INT16)PreRandom(100) < iChance)
1348 			{
1349 				pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestFriend,AI_ACTION_SEEK_FRIEND);
1350 
1351 				if (pSoldier->usActionData != NOWHERE)
1352 				{
1353 					if (fClimb && pSoldier->usActionData == sClosestFriend)
1354 					{
1355 						// need to climb AND have enough APs to get there this turn
1356 						return( AI_ACTION_MOVE_TO_CLIMB );
1357 					}
1358 
1359 					return(AI_ACTION_SEEK_FRIEND);
1360 				}
1361 			}
1362 		}
1363 
1364 
1365 		////////////////////////////////////////////////////////////////////////////
1366 		// TAKE BEST NEARBY COVER FROM THE NOISE GENERATING GRIDNO
1367 		////////////////////////////////////////////////////////////////////////////
1368 
1369 		if (!SkipCoverCheck && gfTurnBasedAI) // only do in turnbased
1370 		{
1371 			// remember that noise value is negative, and closer to 0 => more important!
1372 			iChance = 25;
1373 			iSneaky = 30;
1374 
1375 			// set base chance according to orders
1376 			switch (pSoldier->bOrders)
1377 			{
1378 				case STATIONARY:     iChance +=  20;  break;
1379 				case ONGUARD:        iChance +=  15;  break;
1380 				case ONCALL:                          break;
1381 				case CLOSEPATROL:    iChance +=  10;  break;
1382 				case RNDPTPATROL:
1383 				case POINTPATROL:                     break;
1384 				case FARPATROL:      iChance +=  -5;  break;
1385 				case SEEKENEMY:      iChance += -20;  break;
1386 			}
1387 
1388 			// modify chance (and whether it's sneaky) by attitude
1389 			switch (pSoldier->bAttitude)
1390 			{
1391 				case DEFENSIVE:      iChance +=  10;  iSneaky +=  15;  break;
1392 				case BRAVESOLO:      iChance += -15;  iSneaky += -20;  break;
1393 				case BRAVEAID:       iChance += -20;  iSneaky += -20;  break;
1394 				case CUNNINGSOLO:    iChance +=  20;  iSneaky +=  30;  break;
1395 				case CUNNINGAID:     iChance +=  15;  iSneaky +=  30;  break;
1396 				case AGGRESSIVE:     iChance += -10;  iSneaky += -10;  break;
1397 				case ATTACKSLAYONLY: iChance += -10;  iSneaky += -10;  break;
1398 			}
1399 
1400 
1401 			// reduce chance if breath is down, less likely to wander around when tired
1402 			iChance -= (100 - pSoldier->bBreath);
1403 
1404 			if ((INT16)PreRandom(100) < iChance)
1405 			{
1406 				pSoldier->bAIMorale = CalcMorale( pSoldier );
1407 				pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iDummy);
1408 
1409 				if (pSoldier->usActionData != NOWHERE)
1410 				{
1411 					return(AI_ACTION_TAKE_COVER);
1412 				}
1413 			}
1414 		}
1415 	}
1416 
1417 	////////////////////////////////////////////////////////////////////////////
1418 	// SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong
1419 	////////////////////////////////////////////////////////////////////////////
1420 	if ((INT16)PreRandom(100) < 50)
1421 	{
1422 		// Skip YELLOW until new situation, 15% extra chance to do GREEN actions
1423 		pSoldier->bBypassToGreen = 15;
1424 		return(DecideActionGreen(pSoldier));
1425 	}
1426 
1427 
1428 	////////////////////////////////////////////////////////////////////////////
1429 	// CROUCH IF NOT CROUCHING ALREADY
1430 	////////////////////////////////////////////////////////////////////////////
1431 
1432 	// if not in water and not already crouched, try to crouch down first
1433 	if (!fCivilian && !PTR_CROUCHED && IsValidStance( pSoldier, ANIM_CROUCH ) )
1434 	{
1435 		if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
1436 		{
1437 			pSoldier->usActionData = ANIM_CROUCH;
1438 			return(AI_ACTION_CHANGE_STANCE);
1439 		}
1440 	}
1441 
1442 
1443 	////////////////////////////////////////////////////////////////////////////
1444 	// DO NOTHING: Not enough points left to move, so save them for next turn
1445 	////////////////////////////////////////////////////////////////////////////
1446 	// by default, if everything else fails, just stands in place without turning
1447 	pSoldier->usActionData = NOWHERE;
1448 	return(AI_ACTION_NONE);
1449 }
1450 
1451 
DecideActionRed(SOLDIERTYPE * pSoldier,UINT8 ubUnconsciousOK)1452 INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
1453 {
1454 	INT8 bActionReturned;
1455 	INT32 iDummy;
1456 	INT16 iChance,sClosestOpponent,sClosestFriend;
1457 	INT16 sClosestDisturbance, sDistVisible, sCheckGridNo;
1458 	INT8 bInWater, bInDeepWater, bInGas;
1459 	INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0;
1460 	INT8	bHighestWatchLoc;
1461 	ATTACKTYPE BestThrow;
1462 #ifdef AI_TIMING_TEST
1463 	UINT32	uiStartTime, uiEndTime;
1464 #endif
1465 	BOOLEAN fClimb;
1466 	const BOOLEAN fCivilian =
1467 		IsOnCivTeam(pSoldier) &&
1468 		(
1469 			pSoldier->ubCivilianGroup == NON_CIV_GROUP                                                               ||
1470 			(pSoldier->bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) ||
1471 			(FATCIV <= pSoldier->ubBodyType && pSoldier->ubBodyType <= CRIPPLECIV)
1472 		);
1473 
1474 	// if we have absolutely no action points, we can't do a thing under RED!
1475 	if (!pSoldier->bActionPoints)
1476 	{
1477 		pSoldier->usActionData = NOWHERE;
1478 		return(AI_ACTION_NONE);
1479 	}
1480 
1481 	// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
1482 	const UINT8 ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier));
1483 
1484 	// if we're an alerted enemy, and there are panic bombs or a trigger around
1485 	if ((!IsOnCivTeam(pSoldier) || pSoldier->ubProfile == WARDEN) &&
1486 		(
1487 			gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition ||
1488 			pSoldier            == gTacticalStatus.the_chosen_one    ||
1489 			pSoldier->ubProfile == WARDEN
1490 		) &&
1491 		gTacticalStatus.fPanicFlags & (PANIC_BOMBS_HERE | PANIC_TRIGGERS_HERE))
1492 	{
1493 		if (pSoldier->ubProfile == WARDEN && gTacticalStatus.the_chosen_one == NULL)
1494 		{
1495 			PossiblyMakeThisEnemyChosenOne( pSoldier );
1496 		}
1497 
1498 		// do some special panic AI decision making
1499 		bActionReturned = PanicAI(pSoldier,ubCanMove);
1500 
1501 		// if we decided on an action while in there, we're done
1502 		if (bActionReturned != -1)
1503 			return(bActionReturned);
1504 	}
1505 
1506 	if ( pSoldier->ubProfile != NO_PROFILE )
1507 	{
1508 		if ( (pSoldier->ubProfile == QUEEN || pSoldier->ubProfile == JOE) && ubCanMove )
1509 		{
1510 			if ( gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 && !gfUseAlternateQueenPosition )
1511 			{
1512 				bActionReturned = HeadForTheStairCase( pSoldier );
1513 				if ( bActionReturned != AI_ACTION_NONE )
1514 				{
1515 					return( bActionReturned );
1516 				}
1517 			}
1518 		}
1519 	}
1520 
1521 
1522 	// determine if we happen to be in water (in which case we're in BIG trouble!)
1523 	bInWater = Water( pSoldier->sGridNo );
1524 	bInDeepWater = DeepWater( pSoldier->sGridNo );
1525 
1526 	// check if standing in tear gas without a gas mask on
1527 	bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
1528 
1529 	////////////////////////////////////////////////////////////////////////////
1530 	// WHEN LEFT IN GAS, WEAR GAS MASK IF AVAILABLE AND NOT WORN
1531 	////////////////////////////////////////////////////////////////////////////
1532 
1533 	if ( !bInGas && (gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y) )
1534 	{
1535 		// only chance if we happen to be caught with our gas mask off
1536 		if ( PreRandom( 10 ) == 0 && WearGasMaskIfAvailable( pSoldier ) )
1537 		{
1538 			// reevaluate
1539 			bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
1540 		}
1541 	}
1542 
1543 	////////////////////////////////////////////////////////////////////////////
1544 	// WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND
1545 	////////////////////////////////////////////////////////////////////////////
1546 
1547 	if (bInGas && ubCanMove)
1548 	{
1549 		pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
1550 
1551 		if (pSoldier->usActionData != NOWHERE)
1552 		{
1553 			return(AI_ACTION_LEAVE_WATER_GAS);
1554 		}
1555 	}
1556 
1557 	if ( fCivilian && !( pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CRIPPLECIV ) )
1558 	{
1559 		if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
1560 		{
1561 			// cower in fear!!
1562 			if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1563 			{
1564 				if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector ) // battle!
1565 				{
1566 					// in battle!
1567 					if ( pSoldier->bLastAction == AI_ACTION_COWER )
1568 					{
1569 						// do nothing
1570 						pSoldier->usActionData = NOWHERE;
1571 						return( AI_ACTION_NONE );
1572 					}
1573 					else
1574 					{
1575 						// set up next action to run away
1576 						pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
1577 						if ( pSoldier->usNextActionData != NOWHERE )
1578 						{
1579 							pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
1580 							pSoldier->usActionData = ANIM_STAND;
1581 							return( AI_ACTION_STOP_COWERING );
1582 						}
1583 						else
1584 						{
1585 							return( AI_ACTION_NONE );
1586 						}
1587 					}
1588 				}
1589 				else
1590 				{
1591 					if ( pSoldier->bNewSituation == NOT_NEW_SITUATION )
1592 					{
1593 						// stop cowering, not in battle, timer expired
1594 						// we have to turn off whatever is necessary to stop status red...
1595 						pSoldier->bAlertStatus = STATUS_GREEN;
1596 						return( AI_ACTION_STOP_COWERING );
1597 					}
1598 					else
1599 					{
1600 						return( AI_ACTION_NONE );
1601 					}
1602 				}
1603 			}
1604 			else
1605 			{
1606 				if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector )
1607 				{
1608 					// battle - cower!!!
1609 					pSoldier->usActionData = ANIM_CROUCH;
1610 					return( AI_ACTION_COWER );
1611 				}
1612 				else // not in battle, cower for a certain length of time
1613 				{
1614 					pSoldier->bNextAction = AI_ACTION_WAIT;
1615 					pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
1616 					pSoldier->usActionData = ANIM_CROUCH;
1617 					return( AI_ACTION_COWER );
1618 				}
1619 			}
1620 		}
1621 	}
1622 
1623 	////////////////////////////////////////////////////////////////////////////
1624 	// WHEN IN THE LIGHT, GET OUT OF THERE!
1625 	////////////////////////////////////////////////////////////////////////////
1626 	bool in_light_at_night = InLightAtNight( pSoldier->sGridNo, pSoldier->bLevel );
1627 	if ( ubCanMove && in_light_at_night && pSoldier->bOrders != STATIONARY )
1628 	{
1629 		pSoldier->usActionData = FindNearbyDarkerSpot( pSoldier );
1630 		if ( pSoldier->usActionData != NOWHERE )
1631 		{
1632 			// move as if leaving water or gas
1633 			return( AI_ACTION_LEAVE_WATER_GAS );
1634 		}
1635 	}
1636 
1637 	if ( fCivilian && !( pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CRIPPLECIV ) )
1638 	{
1639 		if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == ITEM_NOT_FOUND )
1640 		{
1641 			// cower in fear!!
1642 			if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1643 			{
1644 				if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector ) // battle!
1645 				{
1646 					// in battle!
1647 					if ( pSoldier->bLastAction == AI_ACTION_COWER )
1648 					{
1649 						// do nothing
1650 						pSoldier->usActionData = NOWHERE;
1651 						return( AI_ACTION_NONE );
1652 					}
1653 					else
1654 					{
1655 						// set up next action to run away
1656 						pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
1657 						if ( pSoldier->usNextActionData != NOWHERE )
1658 						{
1659 							pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
1660 							pSoldier->usActionData = ANIM_STAND;
1661 							return( AI_ACTION_STOP_COWERING );
1662 						}
1663 						else
1664 						{
1665 							return( AI_ACTION_NONE );
1666 						}
1667 					}
1668 				}
1669 				else
1670 				{
1671 					if ( pSoldier->bNewSituation == NOT_NEW_SITUATION )
1672 					{
1673 						// stop cowering, not in battle, timer expired
1674 						// we have to turn off whatever is necessary to stop status red...
1675 						pSoldier->bAlertStatus = STATUS_GREEN;
1676 						return( AI_ACTION_STOP_COWERING );
1677 					}
1678 					else
1679 					{
1680 						return( AI_ACTION_NONE );
1681 					}
1682 				}
1683 			}
1684 			else
1685 			{
1686 				if ( gfTurnBasedAI || gTacticalStatus.fEnemyInSector )
1687 				{
1688 					// battle - cower!!!
1689 					pSoldier->usActionData = ANIM_CROUCH;
1690 					return( AI_ACTION_COWER );
1691 				}
1692 				else // not in battle, cower for a certain length of time
1693 				{
1694 					pSoldier->bNextAction = AI_ACTION_WAIT;
1695 					pSoldier->usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY;
1696 					pSoldier->usActionData = ANIM_CROUCH;
1697 					return( AI_ACTION_COWER );
1698 				}
1699 			}
1700 		}
1701 	}
1702 
1703 	////////////////////////////////////////////////////////////////////////
1704 	// IF POSSIBLE, FIRE LONG RANGE WEAPONS AT TARGETS REPORTED BY RADIO
1705 	////////////////////////////////////////////////////////////////////////
1706 
1707 	// can't do this in realtime, because the player could be shooting a gun or whatever at the same time!
1708 	if (gfTurnBasedAI && !fCivilian && !bInWater && !bInGas && !(pSoldier->uiStatusFlags & SOLDIER_BOXER) && (CanNPCAttack(pSoldier) == TRUE))
1709 	{
1710 		BestThrow.ubPossible = FALSE;    // by default, assume Throwing isn't possible
1711 
1712 
1713 		CheckIfTossPossible(pSoldier,&BestThrow);
1714 
1715 		if (BestThrow.ubPossible)
1716 		{
1717 			// if firing mortar make sure we have room
1718 			if ( pSoldier->inv[ BestThrow.bWeaponIn ].usItem == MORTAR )
1719 			{
1720 				const UINT8 ubOpponentDir = GetDirectionFromGridNo(BestThrow.sTarget, pSoldier);
1721 
1722 				// Get new gridno!
1723 				sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( ubOpponentDir ) );
1724 
1725 				if ( !OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, ubOpponentDir, pSoldier->usAnimState ) )
1726 				{
1727 					// can't fire!
1728 					BestThrow.ubPossible = FALSE;
1729 
1730 					// try behind us, see if there's room to move back
1731 					sCheckGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(ubOpponentDir)));
1732 					if (OKFallDirection(pSoldier, sCheckGridNo, pSoldier->bLevel, OppositeDirection(ubOpponentDir), pSoldier->usAnimState))
1733 					{
1734 						pSoldier->usActionData = sCheckGridNo;
1735 						return( AI_ACTION_GET_CLOSER );
1736 					}
1737 				}
1738 			}
1739 
1740 			// then do it!  The functions have already made sure that we have a
1741 			// pair of worthy opponents, etc., so we're not just wasting our time
1742 
1743 			// if necessary, swap the usItem from holster into the hand position
1744 			if (BestThrow.bWeaponIn != HANDPOS)
1745 				RearrangePocket(pSoldier,HANDPOS,BestThrow.bWeaponIn,FOREVER);
1746 
1747 			pSoldier->usActionData = BestThrow.sTarget;
1748 			pSoldier->bAimTime			= BestThrow.ubAimTime;
1749 
1750 			return(AI_ACTION_TOSS_PROJECTILE);
1751 		}
1752 		else		// toss/throw/launch not possible
1753 		{
1754 			// if this dude has a longe-range weapon on him (longer than normal
1755 			// sight range), and there's at least one other team-mate around, and
1756 			// spotters haven't already been called for, then DO SO!
1757 
1758 			if ( BestThrow.bWeaponIn != NO_SLOT &&
1759 				( CalcMaxTossRange( pSoldier, pSoldier->inv[BestThrow.bWeaponIn].usItem, TRUE ) > MaxDistanceVisible() ) &&
1760 				(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) &&
1761 				(gTacticalStatus.ubSpottersCalledForBy == NOBODY))
1762 			{
1763 				// then call for spotters!  Uses up the rest of his turn (whatever
1764 				// that may be), but from now on, BLACK AI NPC may radio sightings!
1765 				gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID;
1766 				pSoldier->bActionPoints = 0;
1767 				pSoldier->usActionData = NOWHERE;
1768 				return(AI_ACTION_NONE);
1769 			}
1770 		}
1771 	}
1772 
1773 
1774 	////////////////////////////////////////////////////////////////////////
1775 	// CROUCH & REST IF RUNNING OUT OF BREATH
1776 	////////////////////////////////////////////////////////////////////////
1777 
1778 	// if our breath is running a bit low, and we're not in water or under fire
1779 	if ((pSoldier->bBreath < 25) && !bInWater && !pSoldier->bUnderFire)
1780 	{
1781 		// if not already crouched, try to crouch down first
1782 		if (!fCivilian && !PTR_CROUCHED && IsValidStance( pSoldier, ANIM_CROUCH ) )
1783 		{
1784 			if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
1785 			{
1786 				pSoldier->usActionData = ANIM_CROUCH;
1787 				return(AI_ACTION_CHANGE_STANCE);
1788 			}
1789 		}
1790 		pSoldier->usActionData = NOWHERE;
1791 		return(AI_ACTION_NONE);
1792 	}
1793 
1794 
1795 	// calculate our morale
1796 	pSoldier->bAIMorale = CalcMorale(pSoldier);
1797 
1798 	// if a guy is feeling REALLY discouraged, he may continue to run like hell
1799 	if ((pSoldier->bAIMorale == MORALE_HOPELESS) && ubCanMove)
1800 	{
1801 		////////////////////////////////////////////////////////////////////////
1802 		// RUN AWAY TO SPOT FARTHEST FROM KNOWN THREATS (ONLY IF MORALE HOPELESS)
1803 		////////////////////////////////////////////////////////////////////////
1804 
1805 		// look for best place to RUN AWAY to (farthest from the closest threat)
1806 		pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
1807 
1808 		if (pSoldier->usActionData != NOWHERE)
1809 		{
1810 			return(AI_ACTION_RUN_AWAY);
1811 		}
1812 	}
1813 
1814 
1815 	////////////////////////////////////////////////////////////////////////////
1816 	// RADIO RED ALERT: determine %chance to call others and report contact
1817 	////////////////////////////////////////////////////////////////////////////
1818 
1819 	// if we're a computer merc, and we have the action points remaining to RADIO
1820 	// (we never want NPCs to choose to radio if they would have to wait a turn)
1821 	if ( !fCivilian && (pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
1822 	{
1823 
1824 
1825 		// if there hasn't been an initial RED ALERT yet in this sector
1826 		if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger() )
1827 			// since I'm at STATUS RED, I obviously know we're being invaded!
1828 			iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ];
1829 		else // subsequent radioing (only to update enemy positions, request help)
1830 			// base chance depends on how much new info we have to radio to the others
1831 			iChance = 10 * WhatIKnowThatPublicDont(pSoldier,FALSE);  // use 10 * for RED alert
1832 
1833 		// if I actually know something they don't and I ain't swimming (deep water)
1834 		if (iChance && !bInDeepWater)
1835 		{
1836 			// modify base chance according to orders
1837 			switch (pSoldier->bOrders)
1838 			{
1839 				case STATIONARY:       iChance +=  20;  break;
1840 				case ONGUARD:          iChance +=  15;  break;
1841 				case ONCALL:           iChance +=  10;  break;
1842 				case CLOSEPATROL:                       break;
1843 				case RNDPTPATROL:
1844 				case POINTPATROL:      iChance +=  -5;  break;
1845 				case FARPATROL:        iChance += -10;  break;
1846 				case SEEKENEMY:        iChance += -20;  break;
1847 			}
1848 
1849 			// modify base chance according to attitude
1850 			switch (pSoldier->bAttitude)
1851 			{
1852 				case DEFENSIVE:        iChance +=  20;  break;
1853 				case BRAVESOLO:        iChance += -10;  break;
1854 				case BRAVEAID:                          break;
1855 				case CUNNINGSOLO:      iChance +=  -5;  break;
1856 				case CUNNINGAID:                        break;
1857 				case AGGRESSIVE:       iChance += -20;  break;
1858 				case ATTACKSLAYONLY:		iChance = 0;
1859 			}
1860 
1861 			if ( (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) && !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition)
1862 			{
1863 				// ignore morale (which could be really high
1864 			}
1865 			else
1866 			{
1867 				// modify base chance according to morale
1868 				switch (pSoldier->bAIMorale)
1869 				{
1870 					case MORALE_HOPELESS:  iChance *= 3;    break;
1871 					case MORALE_WORRIED:   iChance *= 2;    break;
1872 					case MORALE_NORMAL:                     break;
1873 					case MORALE_CONFIDENT: iChance /= 2;    break;
1874 					case MORALE_FEARLESS:  iChance /= 3;    break;
1875 				}
1876 			}
1877 
1878 			if ((INT16) PreRandom(100) < iChance)
1879 			{
1880 				return(AI_ACTION_RED_ALERT);
1881 			}
1882 		}
1883 	}
1884 
1885 	if ( !TANK( pSoldier ) )
1886 	{
1887 		////////////////////////////////////////////////////////////////////////////
1888 		// MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING
1889 		////////////////////////////////////////////////////////////////////////////
1890 
1891 		// if we can move at least 1 square's worth
1892 		// and have more APs than we want to reserve
1893 		if (ubCanMove && pSoldier->bActionPoints > MAX_AP_CARRIED && !fCivilian)
1894 		{
1895 			if (fCivilian)
1896 			{
1897 				// only interested in hiding out...
1898 				bSeekPts = -99;
1899 				bHelpPts = -99;
1900 				bHidePts =  +1;
1901 			}
1902 			else
1903 			{
1904 
1905 				// calculate initial points for watch based on highest watch loc
1906 
1907 				bWatchPts = GetHighestWatchedLocPoints(pSoldier);
1908 				if ( bWatchPts <= 0 )
1909 				{
1910 					// no watching
1911 					bWatchPts = -99;
1912 				}
1913 
1914 				// modify RED movement tendencies according to morale
1915 				switch (pSoldier->bAIMorale)
1916 				{
1917 					case MORALE_HOPELESS:  bSeekPts = -99; bHelpPts = -99; bHidePts  = +1; bWatchPts =	-99; break;
1918 					case MORALE_WORRIED:   bSeekPts += -1; bHelpPts +=  0; bHidePts += +1; bWatchPts +=	1; break;
1919 					case MORALE_NORMAL:    bSeekPts +=  0; bHelpPts +=  0; bHidePts +=  0; bWatchPts +=	0; break;
1920 					case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts +=  0; bHidePts += -1; bWatchPts +=	0; break;
1921 					case MORALE_FEARLESS:  bSeekPts += +1; bHelpPts +=  0; bHidePts =  -1; bWatchPts +=  0; break;
1922 				}
1923 
1924 				// modify tendencies according to orders
1925 				switch (pSoldier->bOrders)
1926 				{
1927 					case STATIONARY:   bSeekPts += -1; bHelpPts += -1; bHidePts += +1; bWatchPts += +1; break;
1928 					case ONGUARD:      bSeekPts += -1; bHelpPts +=  0; bHidePts += +1; bWatchPts += +1; break;
1929 					case CLOSEPATROL:  bSeekPts +=  0; bHelpPts +=  0; bHidePts +=  0; bWatchPts +=  0; break;
1930 					case RNDPTPATROL:  bSeekPts +=  0; bHelpPts +=  0; bHidePts +=  0; bWatchPts +=  0; break;
1931 					case POINTPATROL:  bSeekPts +=  0; bHelpPts +=  0; bHidePts +=  0; bWatchPts +=  0; break;
1932 					case FARPATROL:    bSeekPts +=  0; bHelpPts +=  0; bHidePts +=  0; bWatchPts +=  0; break;
1933 					case ONCALL:       bSeekPts +=  0; bHelpPts += +1; bHidePts += -1; bWatchPts +=  0; break;
1934 					case SEEKENEMY:    bSeekPts += +1; bHelpPts +=  0; bHidePts += -1; bWatchPts += -1; break;
1935 				}
1936 
1937 				// modify tendencies according to attitude
1938 				switch (pSoldier->bAttitude)
1939 				{
1940 					case DEFENSIVE:     bSeekPts += -1; bHelpPts +=  0; bHidePts += +1; bWatchPts += +1; break;
1941 					case BRAVESOLO:     bSeekPts += +1; bHelpPts += -1; bHidePts += -1; bWatchPts += -1; break;
1942 					case BRAVEAID:      bSeekPts += +1; bHelpPts += +1; bHidePts += -1; bWatchPts += -1; break;
1943 					case CUNNINGSOLO:   bSeekPts +=  0; bHelpPts += -1; bHidePts += +1; bWatchPts +=  0; break;
1944 					case CUNNINGAID:    bSeekPts +=  0; bHelpPts += +1; bHidePts += +1; bWatchPts +=  0; break;
1945 					case AGGRESSIVE:    bSeekPts += +1; bHelpPts +=  0; bHidePts += -1; bWatchPts +=  0; break;
1946 					case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts +=  0; bHidePts += -1; bWatchPts +=  0; break;
1947 				}
1948 			}
1949 
1950 			if (!gfTurnBasedAI)
1951 			{
1952 				// don't search for cover
1953 				bHidePts = -99;
1954 			}
1955 
1956 			// while one of the three main RED REACTIONS remains viable
1957 			while ((bSeekPts > -90) || (bHelpPts > -90) || (bHidePts > -90))
1958 			{
1959 				// if SEEKING is possible and at least as desirable as helping or hiding
1960 				if ( (bSeekPts > -90) && (bSeekPts >= bHelpPts) && (bSeekPts >= bHidePts) && (bSeekPts >= bWatchPts ) )
1961 				{
1962 					#ifdef AI_TIMING_TESTS
1963 					uiStartTime = GetJA2Clock();
1964 					#endif
1965 
1966 					// get the location of the closest reachable opponent
1967 					sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb);
1968 
1969 					#ifdef AI_TIMING_TESTS
1970 					uiEndTime = GetJA2Clock();
1971 					guiRedSeekTimeTotal += (uiEndTime - uiStartTime);
1972 					guiRedSeekCounter++;
1973 					#endif
1974 					// if there is an opponent reachable
1975 					if (sClosestDisturbance != NOWHERE)
1976 					{
1977 						//////////////////////////////////////////////////////////////////////
1978 						// SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT
1979 						//////////////////////////////////////////////////////////////////////
1980 
1981 						// try to move towards him
1982 						pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,AI_ACTION_SEEK_OPPONENT);
1983 
1984 						if (pSoldier->usActionData != NOWHERE)
1985 						{
1986 							// Check for a trap
1987 							if (gamepolicy(avoid_ambushes) && !ArmySeesOpponents())
1988 							{
1989 								UINT8 ubWarnLevel = GetNearestRottingCorpseAIWarning(pSoldier->usActionData);
1990 								if (ubWarnLevel > 0)
1991 								{
1992 									// abort! abort!
1993 									pSoldier->usActionData = NOWHERE;
1994 									STLOGD("TacticalAI: soldier #{} avoiding ambush trap on seeing corpses (warning level {})", pSoldier->ubID, ubWarnLevel);
1995 								}
1996 							}
1997 						}
1998 
1999 						// if it's possible
2000 						if (pSoldier->usActionData != NOWHERE)
2001 						{
2002 							// do it!
2003 							if ( fClimb && pSoldier->usActionData == sClosestDisturbance)
2004 							{
2005 								return( AI_ACTION_MOVE_TO_CLIMB );
2006 							}
2007 
2008 							// if we're a cautious sort,
2009 
2010 							// let's be a bit cautious about going right up to a location without enough APs to shoot
2011 							switch( pSoldier->bAttitude )
2012 							{
2013 								case DEFENSIVE:
2014 								case CUNNINGSOLO:
2015 								case CUNNINGAID:
2016 									if ( PythSpacesAway( pSoldier->usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->usActionData, pSoldier->bLevel, sClosestDisturbance, pSoldier->bLevel, (UINT8) MaxDistanceVisible(), TRUE ) )
2017 									{
2018 										// reserve APs for a possible crouch plus a shot
2019 										pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST) + AP_CROUCH), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS );
2020 										if ( pSoldier->usActionData != NOWHERE )
2021 										{
2022 											pSoldier->fAIFlags |= AI_CAUTIOUS;
2023 											pSoldier->bNextAction = AI_ACTION_END_TURN;
2024 											return(AI_ACTION_SEEK_OPPONENT);
2025 										}
2026 									}
2027 									else
2028 									{
2029 										return(AI_ACTION_SEEK_OPPONENT);
2030 									}
2031 									break;
2032 								default:
2033 									return( AI_ACTION_SEEK_OPPONENT );
2034 							}
2035 						}
2036 					}
2037 
2038 					// mark SEEKING as impossible for next time through while loop
2039 					bSeekPts = -99;
2040 				}
2041 
2042 				// if WATCHING is possible and at least as desirable as anything else
2043 				if ((bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) && (bWatchPts >= bHidePts ))
2044 				{
2045 					// take a look at our highest watch point... if it's still visible, turn to face it and then wait
2046 					bHighestWatchLoc = GetHighestVisibleWatchedLoc(pSoldier);
2047 					//sDistVisible =  DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ], gbWatchedLocLevel[ pSoldier->ubID ][ bHighestWatchLoc ] );
2048 					if ( bHighestWatchLoc != -1 )
2049 					{
2050 						// see if we need turn to face that location
2051 						const UINT8 ubOpponentDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, gsWatchedLoc[pSoldier->ubID][bHighestWatchLoc]);
2052 
2053 						// if soldier is not already facing in that direction,
2054 						// and the opponent is close enough that he could possibly be seen
2055 						if ( pSoldier->bDirection != ubOpponentDir && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
2056 						{
2057 							// turn
2058 							pSoldier->usActionData = ubOpponentDir;
2059 							pSoldier->bNextAction = AI_ACTION_END_TURN;
2060 							return(AI_ACTION_CHANGE_FACING);
2061 						}
2062 						else
2063 						{
2064 							// consider at least crouching
2065 							if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_CROUCH ) )
2066 							{
2067 								pSoldier->usActionData = ANIM_CROUCH;
2068 								pSoldier->bNextAction = AI_ACTION_END_TURN;
2069 								return(AI_ACTION_CHANGE_STANCE);
2070 							}
2071 							else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
2072 							{
2073 								// maybe go prone
2074 								if ((PreRandom(2) == 0 || gamepolicy(ai_go_prone_more_often)) && InternalIsValidStance(pSoldier, ubOpponentDir, ANIM_PRONE))
2075 								{
2076 									pSoldier->usActionData = ANIM_PRONE;
2077 									pSoldier->bNextAction = AI_ACTION_END_TURN;
2078 									return( AI_ACTION_CHANGE_STANCE );
2079 								}
2080 								// end turn
2081 								return( AI_ACTION_END_TURN );
2082 							}
2083 						}
2084 
2085 					}
2086 					bWatchPts = -99;
2087 
2088 				}
2089 
2090 
2091 				// if HELPING is possible and at least as desirable as seeking or hiding
2092 				if ((bHelpPts > -90) && (bHelpPts >= bSeekPts) && (bHelpPts >= bHidePts) && (bHelpPts >= bWatchPts ))
2093 				{
2094 					#ifdef AI_TIMING_TESTS
2095 					uiStartTime = GetJA2Clock();
2096 					#endif
2097 					sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb );
2098 					#ifdef AI_TIMING_TESTS
2099 					uiEndTime = GetJA2Clock();
2100 
2101 					guiRedHelpTimeTotal += (uiEndTime - uiStartTime);
2102 					guiRedHelpCounter++;
2103 					#endif
2104 
2105 					if (sClosestFriend != NOWHERE)
2106 					{
2107 						//////////////////////////////////////////////////////////////////////
2108 						// GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED
2109 						//////////////////////////////////////////////////////////////////////
2110 						pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestFriend,AI_ACTION_SEEK_OPPONENT);
2111 
2112 						if (pSoldier->usActionData != NOWHERE)
2113 						{
2114 							if (fClimb && pSoldier->usActionData == sClosestFriend)
2115 							{
2116 								return( AI_ACTION_MOVE_TO_CLIMB );
2117 							}
2118 							return(AI_ACTION_SEEK_FRIEND);
2119 						}
2120 					}
2121 
2122 					// mark SEEKING as impossible for next time through while loop
2123 					bHelpPts = -99;
2124 				}
2125 
2126 
2127 				// if HIDING is possible and at least as desirable as seeking or helping
2128 				if ((bHidePts > -90) && (bHidePts >= bSeekPts) && (bHidePts >= bHelpPts) && (bHidePts >= bWatchPts ))
2129 				{
2130 					sClosestOpponent = ClosestKnownOpponent( pSoldier, NULL, NULL );
2131 					// if an opponent is known (not necessarily reachable or conscious)
2132 					if (!SkipCoverCheck && sClosestOpponent != NOWHERE )
2133 					{
2134 						//////////////////////////////////////////////////////////////////////
2135 						// TAKE BEST NEARBY COVER FROM ALL KNOWN OPPONENTS
2136 						//////////////////////////////////////////////////////////////////////
2137 						#ifdef AI_TIMING_TESTS
2138 						uiStartTime = GetJA2Clock();
2139 						#endif
2140 
2141 						pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iDummy);
2142 						#ifdef AI_TIMING_TESTS
2143 						uiEndTime = GetJA2Clock();
2144 
2145 						guiRedHideTimeTotal += (uiEndTime - uiStartTime);
2146 						guiRedHideCounter++;
2147 						#endif
2148 
2149 						// let's be a bit cautious about going right up to a location without enough APs to shoot
2150 						if ( pSoldier->usActionData != NOWHERE )
2151 						{
2152 							sClosestDisturbance = ClosestReachableDisturbance(pSoldier, ubUnconsciousOK, &fClimb);
2153 							if ( sClosestDisturbance != NOWHERE && ( SpacesAway( pSoldier->usActionData, sClosestDisturbance ) < 5 || SpacesAway( pSoldier->usActionData, sClosestDisturbance ) + 5 < SpacesAway( pSoldier->sGridNo, sClosestDisturbance ) ) )
2154 							{
2155 								// either moving significantly closer or into very close range
2156 								// ensure will we have enough APs for a possible crouch plus a shot
2157 								if ( InternalGoAsFarAsPossibleTowards( pSoldier, pSoldier->usActionData, (INT8) (MinAPsToAttack( pSoldier, sClosestOpponent, ADDTURNCOST) + AP_CROUCH), AI_ACTION_TAKE_COVER, 0 ) == pSoldier->usActionData )
2158 								{
2159 									return(AI_ACTION_TAKE_COVER);
2160 								}
2161 							}
2162 							else
2163 							{
2164 								return(AI_ACTION_TAKE_COVER);
2165 							}
2166 						}
2167 
2168 					}
2169 
2170 					// mark HIDING as impossible for next time through while loop
2171 					bHidePts = -99;
2172 				}
2173 			}
2174 		}
2175 
2176 
2177 		////////////////////////////////////////////////////////////////////////////
2178 		// NOTHING USEFUL POSSIBLE!  IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY
2179 		////////////////////////////////////////////////////////////////////////////
2180 
2181 		// if we're currently under fire (presumably, attacker is hidden)
2182 		if (pSoldier->bUnderFire || fCivilian)
2183 		{
2184 			// only try to run if we've actually been hit recently & noticably so
2185 			// otherwise, presumably our current cover is pretty good & sufficient
2186 			if (pSoldier->bShock > 0 || fCivilian)
2187 			{
2188 				// look for best place to RUN AWAY to (farthest from the closest threat)
2189 				pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
2190 
2191 				if (pSoldier->usActionData != NOWHERE)
2192 				{
2193 					return(AI_ACTION_RUN_AWAY);
2194 				}
2195 			}
2196 
2197 
2198 			////////////////////////////////////////////////////////////////////////////
2199 			// UNDER FIRE, DON'T WANNA/CAN'T RUN AWAY, SO CROUCH
2200 			////////////////////////////////////////////////////////////////////////////
2201 
2202 
2203 			// if not in water and not already crouched
2204 			if (!fCivilian )
2205 			{
2206 				if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND && IsValidStance( pSoldier, ANIM_CROUCH ) )
2207 				{
2208 					if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
2209 					{
2210 						return(AI_ACTION_CHANGE_STANCE);
2211 					}
2212 				}
2213 				else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE )
2214 				{
2215 					// maybe go prone
2216 					if ( PreRandom( 2 ) == 0 && IsValidStance( pSoldier, ANIM_PRONE ) )
2217 					{
2218 						pSoldier->usActionData = ANIM_PRONE;
2219 						return( AI_ACTION_CHANGE_STANCE );
2220 					}
2221 				}
2222 			}
2223 		}
2224 	}
2225 
2226 
2227 	////////////////////////////////////////////////////////////////////////////
2228 	// LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN
2229 	////////////////////////////////////////////////////////////////////////////
2230 
2231 	if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
2232 	{
2233 		// determine the location of the known closest opponent
2234 		// (don't care if he's conscious, don't care if he's reachable at all)
2235 		sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
2236 
2237 		if (sClosestOpponent != NOWHERE)
2238 		{
2239 			// determine direction from this soldier to the closest opponent
2240 			const UINT8 ubOpponentDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestOpponent);
2241 
2242 			// if soldier is not already facing in that direction,
2243 			// and the opponent is close enough that he could possibly be seen
2244 			// note, have to change this to use the level returned from ClosestKnownOpponent
2245 			sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sClosestOpponent, 0 );
2246 
2247 			if ((pSoldier->bDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible))
2248 			{
2249 				// set base chance according to orders
2250 				if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
2251 					iChance = 50;
2252 				else           // all other orders
2253 					iChance = 25;
2254 
2255 				if (pSoldier->bAttitude == DEFENSIVE)
2256 					iChance += 25;
2257 
2258 				if ( TANK( pSoldier ) )
2259 				{
2260 					iChance += 50;
2261 				}
2262 
2263 				if ((INT16)PreRandom(100) < iChance && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
2264 				{
2265 					pSoldier->usActionData = ubOpponentDir;
2266 					return(AI_ACTION_CHANGE_FACING);
2267 				}
2268 			}
2269 		}
2270 	}
2271 
2272 	if ( TANK( pSoldier ) )
2273 	{
2274 		// try turning in a random direction as we still can't see anyone.
2275 		if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
2276 		{
2277 			sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
2278 			UINT8 ubOpponentDir;
2279 			if ( sClosestDisturbance != NOWHERE )
2280 			{
2281 				ubOpponentDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestDisturbance);
2282 				if ( pSoldier->bDirection == ubOpponentDir )
2283 				{
2284 					ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS );
2285 				}
2286 			}
2287 			else
2288 			{
2289 				ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS );
2290 			}
2291 
2292 			if ( (pSoldier->bDirection != ubOpponentDir) )
2293 			{
2294 				if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints || (INT16)PreRandom(100) < 60) && InternalIsValidStance( pSoldier, ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
2295 				{
2296 					pSoldier->usActionData = ubOpponentDir;
2297 
2298 					// limit turning a bit... if the last thing we did was also a turn, add a 60% chance of this being our last turn
2299 					if ( pSoldier->bLastAction == AI_ACTION_CHANGE_FACING && PreRandom( 100 ) < 60 )
2300 					{
2301 						if ( gfTurnBasedAI )
2302 						{
2303 							pSoldier->bNextAction = AI_ACTION_END_TURN;
2304 						}
2305 						else
2306 						{
2307 							pSoldier->bNextAction = AI_ACTION_WAIT;
2308 							pSoldier->usNextActionData = (UINT16) REALTIME_AI_DELAY;
2309 						}
2310 					}
2311 
2312 					return(AI_ACTION_CHANGE_FACING);
2313 				}
2314 			}
2315 		}
2316 
2317 		// that's it for tanks
2318 		return( AI_ACTION_NONE );
2319 	}
2320 
2321 	////////////////////////////////////////////////////////////////////////////
2322 	// LEAVE THE SECTOR
2323 	////////////////////////////////////////////////////////////////////////////
2324 
2325 	// NOT IMPLEMENTED
2326 
2327 
2328 	////////////////////////////////////////////////////////////////////////////
2329 	// PICKUP A NEARBY ITEM THAT'S USEFUL
2330 	////////////////////////////////////////////////////////////////////////////
2331 
2332 	if ( ubCanMove && !pSoldier->bNeutral && (gfTurnBasedAI || pSoldier->bTeam == ENEMY_TEAM ) )
2333 	{
2334 		pSoldier->bAction = SearchForItems(*pSoldier, SEARCH_GENERAL_ITEMS, pSoldier->inv[HANDPOS].usItem);
2335 
2336 		if (pSoldier->bAction != AI_ACTION_NONE)
2337 		{
2338 			return( pSoldier->bAction );
2339 		}
2340 	}
2341 
2342 
2343 
2344 	////////////////////////////////////////////////////////////////////////////
2345 	// SEEK CLOSEST FRIENDLY MEDIC
2346 	////////////////////////////////////////////////////////////////////////////
2347 
2348 	// NOT IMPLEMENTED
2349 
2350 
2351 	////////////////////////////////////////////////////////////////////////////
2352 	// GIVE FIRST AID TO A NEARBY INJURED/DYING FRIEND
2353 	////////////////////////////////////////////////////////////////////////////
2354 	// - must be BRAVEAID or CUNNINGAID (medic) ?
2355 
2356 	// NOT IMPLEMENTED
2357 
2358 	/* JULY 29, 1996 - Decided that this was a bad idea, after watching a civilian
2359 		start a random patrol while 2 steps away from a hidden armed opponent...*/
2360 
2361 	////////////////////////////////////////////////////////////////////////////
2362 	// SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends
2363 	////////////////////////////////////////////////////////////////////////////
2364 
2365 	// if not in combat or under fire, and we COULD have moved, just chose not to
2366 	if ( (pSoldier->bAlertStatus != STATUS_BLACK) && !pSoldier->bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( ClosestReachableDisturbance(pSoldier, TRUE, &fClimb) == NOWHERE) )
2367 	{
2368 		// addition:  if soldier is bleeding then reduce bleeding and do nothing
2369 		if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD )
2370 		{
2371 			// reduce bleeding by 1 point per AP (in RT, APs will get recalculated so it's okay)
2372 			pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - pSoldier->bActionPoints );
2373 			return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime
2374 		}
2375 
2376 		// Skip RED until new situation/next turn, 30% extra chance to do GREEN actions
2377 		pSoldier->bBypassToGreen = 30;
2378 		return(DecideActionGreen(pSoldier));
2379 	}
2380 
2381 
2382 	////////////////////////////////////////////////////////////////////////////
2383 	// CROUCH IF NOT CROUCHING ALREADY
2384 	////////////////////////////////////////////////////////////////////////////
2385 
2386 	// if not in water and not already crouched, try to crouch down first
2387 	if (!fCivilian && !bInWater && (gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND) && IsValidStance( pSoldier, ANIM_CROUCH ) )
2388 	{
2389 		sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
2390 
2391 		if ( (sClosestOpponent != NOWHERE && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) < (MaxDistanceVisible() * 3) / 2 ) || PreRandom( 4 ) == 0 )
2392 		{
2393 			if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
2394 			{
2395 				return(AI_ACTION_CHANGE_STANCE);
2396 			}
2397 		}
2398 	}
2399 
2400 	////////////////////////////////////////////////////////////////////////////
2401 	// IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW AND GO PRONE
2402 	////////////////////////////////////////////////////////////////////////////
2403 
2404 	if ( !fCivilian && pSoldier->bUnderFire && pSoldier->bActionPoints >= (pSoldier->bInitialActionPoints - GetAPsToLook( pSoldier ) ) && IsValidStance( pSoldier, ANIM_PRONE ) )
2405 	{
2406 		sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
2407 		if ( sClosestDisturbance != NOWHERE )
2408 		{
2409 			const UINT8 ubOpponentDir = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestDisturbance);
2410 			if ( pSoldier->bDirection != ubOpponentDir )
2411 			{
2412 				if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints )
2413 				{
2414 					pSoldier->usActionData = ubOpponentDir;
2415 					return( AI_ACTION_CHANGE_FACING );
2416 				}
2417 			}
2418 			else if ( (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_PRONE ) <= pSoldier->bActionPoints ) && InternalIsValidStance( pSoldier, ubOpponentDir, ANIM_PRONE ) )
2419 			{
2420 				// go prone, end turn
2421 				pSoldier->bNextAction = AI_ACTION_END_TURN;
2422 				pSoldier->usActionData = ANIM_PRONE;
2423 				return( AI_ACTION_CHANGE_STANCE );
2424 			}
2425 		}
2426 	}
2427 
2428 
2429 	////////////////////////////////////////////////////////////////////////////
2430 	// DO NOTHING: Not enough points left to move, so save them for next turn
2431 	////////////////////////////////////////////////////////////////////////////
2432 	pSoldier->usActionData = NOWHERE;
2433 	return(AI_ACTION_NONE);
2434 }
2435 
2436 
DecideActionBlack(SOLDIERTYPE * pSoldier)2437 static INT8 DecideActionBlack(SOLDIERTYPE* pSoldier)
2438 {
2439 	INT32	iCoverPercentBetter, iOffense, iDefense, iChance;
2440 	INT16	sClosestOpponent,sBestCover = NOWHERE;
2441 	INT16	sClosestDisturbance;
2442 	UINT8	ubMinAPCost,ubCanMove;
2443 	INT8		bInDeepWater,bInGas;
2444 	UINT8	ubBestAttackAction = AI_ACTION_NONE;
2445 	INT8		bCanAttack,bActionReturned;
2446 	INT8		bWeaponIn;
2447 	BOOLEAN	fTryPunching = FALSE;
2448 
2449 	ATTACKTYPE BestShot,BestThrow,BestStab,BestAttack;
2450 	const BOOLEAN fCivilian =
2451 		IsOnCivTeam(pSoldier) &&
2452 		(
2453 			pSoldier->ubCivilianGroup == NON_CIV_GROUP ||
2454 			pSoldier->bNeutral                         ||
2455 			(FATCIV <= pSoldier->ubBodyType && pSoldier->ubBodyType <= CRIPPLECIV)
2456 		);
2457 	UINT8	ubStanceCost;
2458 	BOOLEAN fChangeStanceFirst; // before firing
2459 	BOOLEAN fClimb;
2460 	UINT8	ubOpponentDir;
2461 	INT16	sCheckGridNo;
2462 
2463 	BOOLEAN fAllowCoverCheck = FALSE;
2464 
2465 	UINT8 ubBestStance = (UINT8)-1; // XXX HACK000E
2466 
2467 	// if we have absolutely no action points, we can't do a thing under BLACK!
2468 	if (!pSoldier->bActionPoints)
2469 	{
2470 		pSoldier->usActionData = NOWHERE;
2471 		return(AI_ACTION_NONE);
2472 	}
2473 
2474 	// can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE)
2475 	ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier));
2476 
2477 	if ((pSoldier->bTeam == ENEMY_TEAM || pSoldier->ubProfile == WARDEN) &&
2478 		gTacticalStatus.fPanicFlags    &  PANIC_TRIGGERS_HERE            &&
2479 		gTacticalStatus.the_chosen_one == NULL)
2480 	{
2481 		INT8 bPanicTrigger;
2482 
2483 		bPanicTrigger = ClosestPanicTrigger( pSoldier );
2484 		// if it's an alarm trigger and team is alerted, ignore it
2485 		if ( bPanicTrigger != -1 && !(gTacticalStatus.bPanicTriggerIsAlarm[ bPanicTrigger ] && gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) && PythSpacesAway( pSoldier->sGridNo, gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ] ) < 10)
2486 		{
2487 			PossiblyMakeThisEnemyChosenOne( pSoldier );
2488 		}
2489 	}
2490 
2491 	// if this soldier is the "Chosen One" (enemies only)
2492 	if (pSoldier == gTacticalStatus.the_chosen_one)
2493 	{
2494 		// do some special panic AI decision making
2495 		bActionReturned = PanicAI(pSoldier,ubCanMove);
2496 
2497 		// if we decided on an action while in there, we're done
2498 		if (bActionReturned != -1)
2499 			return(bActionReturned);
2500 	}
2501 
2502 	if ( pSoldier->ubProfile != NO_PROFILE )
2503 	{
2504 		// if they see enemies, the Queen will keep going to the staircase, but Joe will fight
2505 		if ( (pSoldier->ubProfile == QUEEN) && ubCanMove )
2506 		{
2507 			if ( gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 && !gfUseAlternateQueenPosition )
2508 			{
2509 				bActionReturned = HeadForTheStairCase( pSoldier );
2510 				if ( bActionReturned != AI_ACTION_NONE )
2511 				{
2512 					return( bActionReturned );
2513 				}
2514 			}
2515 		}
2516 	}
2517 
2518 	if ( pSoldier->uiStatusFlags & SOLDIER_BOXER )
2519 	{
2520 		if ( gTacticalStatus.bBoxingState == PRE_BOXING )
2521 		{
2522 			return( DecideActionBoxerEnteringRing( pSoldier ) );
2523 		}
2524 		else if ( gTacticalStatus.bBoxingState == BOXING )
2525 		{
2526 			bInDeepWater = FALSE;
2527 			bInGas = FALSE;
2528 
2529 			// calculate our morale
2530 			pSoldier->bAIMorale = CalcMorale(pSoldier);
2531 			// and continue on...
2532 		}
2533 		else //????
2534 		{
2535 			return( AI_ACTION_NONE );
2536 		}
2537 	}
2538 	else
2539 	{
2540 
2541 		// determine if we happen to be in water (in which case we're in BIG trouble!)
2542 		bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo );
2543 
2544 		// check if standing in tear gas without a gas mask on
2545 		bInGas = InGasOrSmoke( pSoldier, pSoldier->sGridNo );
2546 
2547 		// calculate our morale
2548 		pSoldier->bAIMorale = CalcMorale(pSoldier);
2549 
2550 		////////////////////////////////////////////////////////////////////////////
2551 		// WHEN LEFT IN GAS, WEAR GAS MASK IF AVAILABLE AND NOT WORN
2552 		////////////////////////////////////////////////////////////////////////////
2553 
2554 		if ( !bInGas && (gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y) )
2555 		{
2556 			// only chance if we happen to be caught with our gas mask off
2557 			if ( PreRandom( 10 ) == 0 && WearGasMaskIfAvailable( pSoldier ) )
2558 			{
2559 				bInGas = FALSE;
2560 			}
2561 		}
2562 
2563 		////////////////////////////////////////////////////////////////////////////
2564 		// IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY
2565 		////////////////////////////////////////////////////////////////////////////
2566 
2567 		// if we're desperately short on breath (it's OK if we're in water, though!)
2568 		if (bInGas || (pSoldier->bBreath < 5))
2569 		{
2570 			// if soldier has enough APs left to move at least 1 square's worth
2571 			if (ubCanMove)
2572 			{
2573 				// look for best place to RUN AWAY to (farthest from the closest threat)
2574 				pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
2575 
2576 				if (pSoldier->usActionData != NOWHERE)
2577 				{
2578 						return(AI_ACTION_RUN_AWAY);
2579 				}
2580 			}
2581 
2582 			// REALLY tired, can't get away, force soldier's morale to hopeless state
2583 			pSoldier->bAIMorale = MORALE_HOPELESS;
2584 		}
2585 
2586 	}
2587 
2588 
2589 
2590 	////////////////////////////////////////////////////////////////////////////
2591 	// STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND
2592 	////////////////////////////////////////////////////////////////////////////
2593 
2594 	// if soldier in water/gas has enough APs left to move at least 1 square
2595 	if ( ( bInDeepWater || bInGas ) && ubCanMove)
2596 	{
2597 		pSoldier->usActionData = FindNearestUngassedLand(pSoldier);
2598 
2599 		if (pSoldier->usActionData != NOWHERE)
2600 		{
2601 			return(AI_ACTION_LEAVE_WATER_GAS);
2602 		}
2603 
2604 		// couldn't find ANY land within 25 tiles(!), this should never happen...
2605 
2606 		// look for best place to RUN AWAY to (farthest from the closest threat)
2607 		pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
2608 
2609 		if (pSoldier->usActionData != NOWHERE)
2610 		{
2611 			return(AI_ACTION_RUN_AWAY);
2612 		}
2613 
2614 		// GIVE UP ON LIFE!  MERCS MUST HAVE JUST CORNERED A HELPLESS ENEMY IN A
2615 		// GAS FILLED ROOM (OR IN WATER MORE THAN 25 TILES FROM NEAREST LAND...)
2616 		pSoldier->bAIMorale = MORALE_HOPELESS;
2617 	}
2618 
2619 	// offer surrender?
2620 
2621 	if ( pSoldier->bTeam == ENEMY_TEAM && pSoldier->bVisible == TRUE && !( gTacticalStatus.fEnemyFlags & ENEMY_OFFERED_SURRENDER ) && pSoldier->bLife >= pSoldier->bLifeMax / 2 )
2622 	{
2623 		if (!IsTeamActive(MILITIA_TEAM) && NumPCsInSector() < 4 && gTacticalStatus.Team[ENEMY_TEAM].bMenInSector >= NumPCsInSector() * 3)
2624 		{
2625 			//if( GetWorldDay() > STARTDAY_ALLOW_PLAYER_CAPTURE_FOR_RESCUE && !( gStrategicStatus.uiFlags & STRATEGIC_PLAYER_CAPTURED_FOR_RESCUE ) )
2626 			{
2627 				if ( gubQuest[ QUEST_HELD_IN_ALMA ] == QUESTNOTSTARTED || ( gubQuest[ QUEST_HELD_IN_ALMA ] == QUESTDONE && gubQuest[ QUEST_INTERROGATION ] == QUESTNOTSTARTED ) )
2628 				{
2629 					gTacticalStatus.fEnemyFlags |= ENEMY_OFFERED_SURRENDER;
2630 					return( AI_ACTION_OFFER_SURRENDER );
2631 				}
2632 			}
2633 		}
2634 	}
2635 
2636 	////////////////////////////////////////////////////////////////////////////
2637 	// SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY
2638 	////////////////////////////////////////////////////////////////////////////
2639 
2640 
2641 	// NPCs in water/tear gas without masks are not permitted to shoot/stab/throw
2642 	if (pSoldier->bActionPoints < 2 || bInDeepWater || bInGas)
2643 	{
2644 		bCanAttack = FALSE;
2645 	}
2646 	else if (pSoldier->uiStatusFlags & SOLDIER_BOXER)
2647 	{
2648 		bCanAttack = TRUE;
2649 		fTryPunching = TRUE;
2650 	}
2651 	else
2652 	{
2653 		do
2654 		{
2655 
2656 			bCanAttack = CanNPCAttack(pSoldier);
2657 			if (bCanAttack != TRUE)
2658 			{
2659 				if (fCivilian)
2660 				{
2661 					if ( ( bCanAttack == NOSHOOT_NOWEAPON) && !(pSoldier->uiStatusFlags & SOLDIER_BOXER) && pSoldier->ubBodyType != COW && pSoldier->ubBodyType != CRIPPLECIV )
2662 					{
2663 						// cower in fear!!
2664 						if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
2665 						{
2666 							if ( pSoldier->bLastAction == AI_ACTION_COWER )
2667 							{
2668 								// do nothing
2669 								pSoldier->usActionData = NOWHERE;
2670 								return( AI_ACTION_NONE );
2671 							}
2672 							else
2673 							{
2674 								// set up next action to run away
2675 								pSoldier->usNextActionData = FindSpotMaxDistFromOpponents( pSoldier );
2676 								if ( pSoldier->usNextActionData != NOWHERE )
2677 								{
2678 									pSoldier->bNextAction = AI_ACTION_RUN_AWAY;
2679 									pSoldier->usActionData = ANIM_STAND;
2680 									return( AI_ACTION_STOP_COWERING );
2681 								}
2682 								else
2683 								{
2684 									return( AI_ACTION_NONE );
2685 								}
2686 							}
2687 						}
2688 						else
2689 						{
2690 							// cower!!!
2691 							pSoldier->usActionData = ANIM_CROUCH;
2692 							return( AI_ACTION_COWER );
2693 						}
2694 					}
2695 				}
2696 				else if (bCanAttack == NOSHOOT_NOAMMO && ubCanMove && !pSoldier->bNeutral)
2697 				{
2698 					// try to find more ammo
2699 					pSoldier->bAction = SearchForItems(*pSoldier, SEARCH_AMMO, pSoldier->inv[HANDPOS].usItem);
2700 
2701 					if (pSoldier->bAction == AI_ACTION_NONE)
2702 					{
2703 						// the current weapon appears is useless right now!
2704 						// (since we got a return code of noammo, we know the hand usItem
2705 						// is our gun)
2706 						pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE;
2707 						// move the gun into another pocket...
2708 						AutoPlaceObject( pSoldier, &(pSoldier->inv[HANDPOS]), FALSE );
2709 					}
2710 					else
2711 					{
2712 						return( pSoldier->bAction );
2713 					}
2714 				}
2715 				else
2716 				{
2717 					bCanAttack = FALSE;
2718 				}
2719 			}
2720 		} while (bCanAttack != TRUE && bCanAttack != FALSE);
2721 
2722 		#ifdef RETREAT_TESTING
2723 		bCanAttack = FALSE;
2724 		#endif
2725 
2726 		if (!bCanAttack)
2727 		{
2728 			if (pSoldier->bAIMorale > MORALE_WORRIED)
2729 			{
2730 				pSoldier->bAIMorale = MORALE_WORRIED;
2731 			}
2732 
2733 			if (!fCivilian)
2734 			{
2735 				// can always attack with HTH as a last resort
2736 				bCanAttack = TRUE;
2737 				fTryPunching = TRUE;
2738 			}
2739 		}
2740 	}
2741 
2742 	// if we don't have a gun, look around for a weapon!
2743 	if (FindAIUsableObjClass( pSoldier, IC_GUN ) == ITEM_NOT_FOUND && ubCanMove && !pSoldier->bNeutral)
2744 	{
2745 		// look around for a gun...
2746 		pSoldier->bAction = SearchForItems(*pSoldier, SEARCH_WEAPONS, pSoldier->inv[HANDPOS].usItem);
2747 		if (pSoldier->bAction != AI_ACTION_NONE )
2748 		{
2749 			return( pSoldier->bAction );
2750 		}
2751 	}
2752 
2753 
2754 	BestShot.ubPossible  = FALSE;	// by default, assume Shooting isn't possible
2755 	BestThrow.ubPossible = FALSE;	// by default, assume Throwing isn't possible
2756 	BestStab.ubPossible  = FALSE;	// by default, assume Stabbing isn't possible
2757 
2758 	BestAttack.ubChanceToReallyHit = 0;
2759 
2760 
2761 	// if we are able attack
2762 	if (bCanAttack)
2763 	{
2764 		pSoldier->bAimShotLocation = AIM_SHOT_RANDOM;
2765 
2766 		//////////////////////////////////////////////////////////////////////////
2767 		// FIRE A GUN AT AN OPPONENT
2768 		//////////////////////////////////////////////////////////////////////////
2769 
2770 		bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
2771 
2772 		if (bWeaponIn != NO_SLOT)
2773 		{
2774 			BestShot.bWeaponIn = bWeaponIn;
2775 			// if it's in another pocket, swap it into his hand temporarily
2776 			if (bWeaponIn != HANDPOS)
2777 			{
2778 				RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
2779 			}
2780 
2781 			// now it better be a gun, or the guy can't shoot (but has other attack(s))
2782 			if (GCM->getItem(pSoldier->inv[HANDPOS].usItem)->getItemClass() == IC_GUN && pSoldier->inv[HANDPOS].bGunStatus >= USABLE)
2783 			{
2784 				// get the minimum cost to attack the same target with this gun
2785 				ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
2786 
2787 				// if we have enough action points to shoot with this gun
2788 				if (pSoldier->bActionPoints >= ubMinAPCost)
2789 				{
2790 					// look around for a worthy target (which sets BestShot.ubPossible)
2791 					CalcBestShot(pSoldier,&BestShot);
2792 
2793 					if (pSoldier->bTeam == OUR_TEAM && BestShot.ubChanceToReallyHit < 30)
2794 					{
2795 						// skip firing, our chance isn't good enough
2796 						BestShot.ubPossible = FALSE;
2797 					}
2798 
2799 					if (BestShot.ubPossible)
2800 					{
2801 						// if the selected opponent is not a threat (unconscious & !serviced)
2802 						// (usually, this means all the guys we see are unconscious, but, on
2803 						//  rare occasions, we may not be able to shoot a healthy guy, too)
2804 						const SOLDIERTYPE* const opp = BestShot.opponent;
2805 						if (opp->bLife < OKLIFE)
2806 						{
2807 							// if our attitude is NOT aggressive
2808 							if ( pSoldier->bAttitude != AGGRESSIVE || BestShot.ubChanceToReallyHit < 60 )
2809 							{
2810 								// get the location of the closest CONSCIOUS reachable opponent
2811 								sClosestDisturbance = ClosestReachableDisturbance(pSoldier,FALSE, &fClimb);
2812 
2813 								// if we found one
2814 								if (sClosestDisturbance != NOWHERE)
2815 								{
2816 									// don't bother checking GRENADES/KNIVES, he can't have conscious targets
2817 									// then make decision as if at alert status RED, but make sure
2818 									// we don't try to SEEK OPPONENT the unconscious guy!
2819 									return(DecideActionRed(pSoldier,FALSE));
2820 								}
2821 								// else kill the guy, he could be the last opponent alive in this sector
2822 							}
2823 							// else aggressive guys will ALWAYS finish off unconscious opponents
2824 						}
2825 
2826 						// now we KNOW FOR SURE that we will do something (shoot, at least)
2827 						NPCDoesAct(pSoldier);
2828 
2829 					}
2830 				}
2831 
2832 				// if it was in his holster, swap it back into his holster for now
2833 				if (bWeaponIn != HANDPOS)
2834 				{
2835 					RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
2836 				}
2837 
2838 			}
2839 		}
2840 
2841 		//////////////////////////////////////////////////////////////////////////
2842 		// THROW A TOSSABLE ITEM AT OPPONENT(S)
2843 		// 	- HTH: THIS NOW INCLUDES FIRING THE GRENADE LAUNCHAR AND MORTAR!
2844 		//////////////////////////////////////////////////////////////////////////
2845 
2846 		// this looks for throwables, and sets BestThrow.ubPossible if it can be done
2847 		//if ( !gfHiddenInterrupt )
2848 		{
2849 			CheckIfTossPossible(pSoldier,&BestThrow);
2850 
2851 			if (BestThrow.ubPossible)
2852 			{
2853 				if ( pSoldier->inv[ BestThrow.bWeaponIn ].usItem == MORTAR )
2854 				{
2855 					ubOpponentDir = (UINT8)GetDirectionFromGridNo( BestThrow.sTarget, pSoldier );
2856 
2857 					// Get new gridno!
2858 					sCheckGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, DirectionInc( ubOpponentDir ) );
2859 
2860 					if ( !OKFallDirection( pSoldier, sCheckGridNo, pSoldier->bLevel, ubOpponentDir, pSoldier->usAnimState ) )
2861 					{
2862 						// can't fire!
2863 						BestThrow.ubPossible = FALSE;
2864 
2865 						// try behind us, see if there's room to move back
2866 						sCheckGridNo = NewGridNo(pSoldier->sGridNo, DirectionInc(OppositeDirection(ubOpponentDir)));
2867 						if (OKFallDirection(pSoldier, sCheckGridNo, pSoldier->bLevel, OppositeDirection(ubOpponentDir), pSoldier->usAnimState))
2868 						{
2869 							pSoldier->usActionData = sCheckGridNo;
2870 
2871 							return( AI_ACTION_GET_CLOSER );
2872 						}
2873 					}
2874 				}
2875 
2876 				if ( BestThrow.ubPossible )
2877 				{
2878 					// now we KNOW FOR SURE that we will do something (throw, at least)
2879 					NPCDoesAct(pSoldier);
2880 				}
2881 			}
2882 		}
2883 
2884 		//////////////////////////////////////////////////////////////////////////
2885 		// GO STAB AN OPPONENT WITH A KNIFE
2886 		//////////////////////////////////////////////////////////////////////////
2887 
2888 		// if soldier has a knife in his hand
2889 		bWeaponIn = FindAIUsableObjClass( pSoldier, (IC_BLADE | IC_THROWING_KNIFE) );
2890 
2891 		// if the soldier does have a usable knife somewhere
2892 		if (bWeaponIn != NO_SLOT)
2893 		{
2894 			BestStab.bWeaponIn = bWeaponIn;
2895 			// if it's in his holster, swap it into his hand temporarily
2896 			if (bWeaponIn != HANDPOS)
2897 			{
2898 				RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
2899 			}
2900 
2901 			// get the minimum cost to attack with this knife
2902 			ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
2903 
2904 			// if we can afford the minimum AP cost to stab with/throw this knife weapon
2905 			if (pSoldier->bActionPoints >= ubMinAPCost)
2906 			{
2907 				// NB throwing knife in hand now
2908 				if ( GCM->getItem(pSoldier->inv[HANDPOS].usItem)->isThrowingKnife() )
2909 				{
2910 					// throwing knife code works like shooting
2911 
2912 					// look around for a worthy target (which sets BestStab.ubPossible)
2913 					CalcBestShot(pSoldier,&BestStab);
2914 
2915 					if (BestStab.ubPossible)
2916 					{
2917 						// if the selected opponent is not a threat (unconscious & !serviced)
2918 						// (usually, this means all the guys we see are unconscious, but, on
2919 						//  rare occasions, we may not be able to shoot a healthy guy, too)
2920 						const SOLDIERTYPE* const opp = BestStab.opponent;
2921 						if (opp->bLife < OKLIFE)
2922 						{
2923 							// don't throw a knife at him.
2924 							BestStab.ubPossible = FALSE;
2925 						}
2926 
2927 						// now we KNOW FOR SURE that we will do something (shoot, at least)
2928 						NPCDoesAct(pSoldier);
2929 					}
2930 				}
2931 				else
2932 				{
2933 					// then look around for a worthy target (which sets BestStab.ubPossible)
2934 					CalcBestStab(pSoldier,&BestStab, TRUE);
2935 
2936 					if (BestStab.ubPossible)
2937 					{
2938 						// now we KNOW FOR SURE that we will do something (stab, at least)
2939 						NPCDoesAct(pSoldier);
2940 					}
2941 				}
2942 
2943 			}
2944 
2945 			// if it was in his holster, swap it back into his holster for now
2946 			if (bWeaponIn != HANDPOS)
2947 			{
2948 				RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
2949 			}
2950 		}
2951 
2952 		//////////////////////////////////////////////////////////////////////////
2953 		// CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE
2954 		//////////////////////////////////////////////////////////////////////////
2955 		if (BestShot.ubPossible)
2956 		{
2957 			BestAttack.iAttackValue = BestShot.iAttackValue;
2958 			ubBestAttackAction = AI_ACTION_FIRE_GUN;
2959 		}
2960 		else
2961 		{
2962 			BestAttack.iAttackValue = 0;
2963 		}
2964 		if (BestStab.ubPossible && ((BestStab.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE)))
2965 		{
2966 			BestAttack.iAttackValue = BestStab.iAttackValue;
2967 			if ( GCM->getItem(pSoldier->inv[BestStab.bWeaponIn].usItem)->isThrowingKnife() )
2968 			{
2969 				ubBestAttackAction = AI_ACTION_THROW_KNIFE;
2970 			}
2971 			else
2972 			{
2973 				ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
2974 			}
2975 		}
2976 		if (BestThrow.ubPossible && ((BestThrow.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE)))
2977 		{
2978 			ubBestAttackAction = AI_ACTION_TOSS_PROJECTILE;
2979 		}
2980 
2981 		if ( ( ubBestAttackAction == AI_ACTION_NONE ) && fTryPunching )
2982 		{
2983 			// nothing (else) to attack with so let's try hand-to-hand
2984 			bWeaponIn = FindObjWithin( pSoldier, NOTHING, HANDPOS, SMALLPOCK8POS );
2985 
2986 			if (bWeaponIn != NO_SLOT)
2987 			{
2988 				BestStab.bWeaponIn = bWeaponIn;
2989 				// if it's in his holster, swap it into his hand temporarily
2990 				if (bWeaponIn != HANDPOS)
2991 				{
2992 					RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
2993 				}
2994 
2995 				// get the minimum cost to attack by HTH
2996 				ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST);
2997 
2998 				// if we can afford the minimum AP cost to use HTH combat
2999 				if (pSoldier->bActionPoints >= ubMinAPCost)
3000 					{
3001 					// then look around for a worthy target (which sets BestStab.ubPossible)
3002 					CalcBestStab(pSoldier,&BestStab, FALSE);
3003 
3004 					if (BestStab.ubPossible)
3005 					{
3006 						// now we KNOW FOR SURE that we will do something (stab, at least)
3007 						NPCDoesAct(pSoldier);
3008 						ubBestAttackAction = AI_ACTION_KNIFE_MOVE;
3009 					}
3010 					}
3011 
3012 				// if it was in his holster, swap it back into his holster for now
3013 				if (bWeaponIn != HANDPOS)
3014 				{
3015 					RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
3016 				}
3017 			}
3018 		}
3019 
3020 
3021 		// copy the information on the best action selected into BestAttack struct
3022 		switch (ubBestAttackAction)
3023 		{
3024 			case AI_ACTION_FIRE_GUN:        BestAttack = BestShot;  break;
3025 			case AI_ACTION_TOSS_PROJECTILE: BestAttack = BestThrow; break;
3026 			case AI_ACTION_THROW_KNIFE:
3027 			case AI_ACTION_KNIFE_MOVE:      BestAttack = BestStab;  break;
3028 
3029 			default:
3030 				// set to empty
3031 				BestAttack = ATTACKTYPE{};
3032 				break;
3033 		}
3034 	}
3035 
3036 	// NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale
3037 	if ( pSoldier->bActionPoints == pSoldier->bInitialActionPoints && ubBestAttackAction == AI_ACTION_FIRE_GUN && (pSoldier->bShock == 0) && (pSoldier->bLife >= pSoldier->bLifeMax / 2) && BestAttack.ubChanceToReallyHit < 30 && ( PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) > GCM->getWeapon( pSoldier->inv[ BestAttack.bWeaponIn].usItem)->usRange / CELL_X_SIZE ) && RangeChangeDesire( pSoldier ) >= 4 )
3038 	{
3039 		// okay, really got to wonder about this... could taking cover be an option?
3040 		if (ubCanMove && pSoldier->bOrders != STATIONARY && !gfHiddenInterrupt &&
3041 			!(pSoldier->uiStatusFlags & SOLDIER_BOXER) )
3042 		{
3043 			// make militia a bit more cautious
3044 			if ( ( (pSoldier->bTeam == MILITIA_TEAM) && (PreRandom( 20 ) > BestAttack.ubChanceToReallyHit) )
3045 				|| ( (pSoldier->bTeam != MILITIA_TEAM) && (PreRandom( 40 ) > BestAttack.ubChanceToReallyHit) ) )
3046 			{
3047 				STLOGD("AI {} allowing cover check, chance to hit is only {}, at range {}", pSoldier->ubID, BestAttack.ubChanceToReallyHit, PythSpacesAway(pSoldier->sGridNo, BestAttack.sTarget));
3048 				// maybe taking cover would be better!
3049 				fAllowCoverCheck = TRUE;
3050 				if ( PreRandom( 10 ) > BestAttack.ubChanceToReallyHit )
3051 				{
3052 					// screw the attack!
3053 					ubBestAttackAction = AI_ACTION_NONE;
3054 				}
3055 			}
3056 		}
3057 
3058 	}
3059 
3060 
3061 	////////////////////////////////////////////////////////////////////////////
3062 	// LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW
3063 	////////////////////////////////////////////////////////////////////////////
3064 
3065 	// if soldier has enough APs left to move at least 1 square's worth,
3066 	// and either he can't attack any more, or his attack did wound someone
3067 	if ( (ubCanMove && !SkipCoverCheck && !gfHiddenInterrupt &&
3068 		((ubBestAttackAction == AI_ACTION_NONE) || pSoldier->bLastAttackHit) &&
3069 		(pSoldier->bTeam != OUR_TEAM || pSoldier->fAIFlags & AI_RTP_OPTION_CAN_SEEK_COVER) &&
3070 		!(pSoldier->uiStatusFlags & SOLDIER_BOXER) )
3071 		|| fAllowCoverCheck )
3072 	{
3073 		sBestCover = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iCoverPercentBetter);
3074 	}
3075 
3076 
3077 #ifdef RETREAT_TESTING
3078 	sBestCover = NOWHERE;
3079 #endif
3080 
3081 	//////////////////////////////////////////////////////////////////////////
3082 	// IF NECESSARY, DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)
3083 	//////////////////////////////////////////////////////////////////////////
3084 
3085 	// if both are possible
3086 	if ((ubBestAttackAction != AI_ACTION_NONE) && (sBestCover != NOWHERE))
3087 	{
3088 		// gotta compare their merits and select the more desirable option
3089 		iOffense = BestAttack.ubChanceToReallyHit;
3090 		iDefense = iCoverPercentBetter;
3091 
3092 		// based on how we feel about the situation, decide whether to attack first
3093 		switch (pSoldier->bAIMorale)
3094 		{
3095 			case MORALE_FEARLESS:
3096 				iOffense += iOffense / 2;	// increase 50%
3097 				break;
3098 
3099 			case MORALE_CONFIDENT:
3100 				iOffense += iOffense / 4;	// increase 25%
3101 				break;
3102 
3103 			case MORALE_NORMAL:
3104 				break;
3105 
3106 			case MORALE_WORRIED:
3107 				iDefense += iDefense / 4;	// increase 25%
3108 				break;
3109 
3110 			case MORALE_HOPELESS:
3111 				iDefense += iDefense / 2;	// increase 50%
3112 				break;
3113 		}
3114 
3115 
3116 		// smart guys more likely to try to stay alive, dolts more likely to shoot!
3117 		if (pSoldier->bWisdom >= 80)
3118 			iDefense += 10;
3119 		else if (pSoldier->bWisdom < 50)
3120 			iDefense -= 10;
3121 
3122 		// some orders are more offensive, others more defensive
3123 		if (pSoldier->bOrders == SEEKENEMY)
3124 			iOffense += 10;
3125 		else if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD))
3126 			iDefense += 10;
3127 
3128 		switch (pSoldier->bAttitude)
3129 		{
3130 			case DEFENSIVE:		iDefense += 20; break;
3131 			case BRAVESOLO:		iDefense -= 10; break;
3132 			case BRAVEAID:			iDefense -= 10; break;
3133 			case CUNNINGSOLO:	iDefense += 10; break;
3134 			case CUNNINGAID:		iDefense += 10; break;
3135 			case AGGRESSIVE:		iOffense += 20; break;
3136 			case ATTACKSLAYONLY:iOffense += 30; break;
3137 		}
3138 		SLOGD(ST::format("{} - CHOICE: iOffense = {}, iDefense = {}\n",
3139 			pSoldier->name, iOffense,iDefense));
3140 
3141 		// if his defensive instincts win out, forget all about the attack
3142 		if (iDefense > iOffense)
3143 			ubBestAttackAction = AI_ACTION_NONE;
3144 	}
3145 
3146 
3147 	// if attack is still desirable (meaning it's also preferred to taking cover)
3148 	if (ubBestAttackAction != AI_ACTION_NONE)
3149 	{
3150 		// if we wanted to be REALLY mean, we could look at chance to hit and decide whether
3151 		// to shoot at the head...
3152 
3153 		fChangeStanceFirst = FALSE;
3154 
3155 		// default settings
3156 		pSoldier->bAimTime = BestAttack.ubAimTime;
3157 		pSoldier->bDoBurst = 0;
3158 
3159 		if (ubBestAttackAction == AI_ACTION_FIRE_GUN)
3160 		{
3161 			// Do we need to change stance?  NB We'll have to ready our gun again
3162 			if ( !TANK( pSoldier ) && ( pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) ) >= (AP_CROUCH + GetAPsToReadyWeapon( pSoldier, pSoldier->usAnimState ) ) )
3163 			{
3164 				// since the AI considers shooting chance from standing primarily, if we are not
3165 				// standing we should always consider a stance change
3166 				if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_STAND )
3167 				{
3168 					iChance = 100;
3169 				}
3170 				else
3171 				{
3172 					iChance = 50;
3173 					switch (pSoldier->bAttitude)
3174 					{
3175 						case DEFENSIVE:		iChance += 20; break;
3176 						case BRAVESOLO:		iChance -= 10; break;
3177 						case BRAVEAID:			iChance -= 10; break;
3178 						case CUNNINGSOLO:	iChance += 10; break;
3179 						case CUNNINGAID:		iChance += 10; break;
3180 						case AGGRESSIVE:		iChance -= 20; break;
3181 						case ATTACKSLAYONLY: iChance -= 30; break;
3182 					}
3183 				}
3184 
3185 				if ( (INT32)PreRandom( 100 ) < iChance || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ) <= MIN_PRONE_RANGE )
3186 				{
3187 					// first get the direction, as we will need to pass that in to ShootingStanceChange
3188 					const INT8 bDirection = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, BestAttack.sTarget);
3189 
3190 					ubBestStance = ShootingStanceChange( pSoldier, &BestAttack, bDirection );
3191 					if (ubBestStance != 0)
3192 					{
3193 						// change stance first!
3194 						if ( pSoldier->bDirection != bDirection && InternalIsValidStance( pSoldier, bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
3195 						{
3196 							// we're not facing towards him, so turn first!
3197 							pSoldier->usActionData = bDirection;
3198 							return(AI_ACTION_CHANGE_FACING);
3199 						}
3200 
3201 //						pSoldier->usActionData = ubBestStance;
3202 
3203 						// attack after we change stance
3204 						// we don't just return here because we want to check whether to
3205 						// burst first
3206 						fChangeStanceFirst = TRUE;
3207 
3208 						// account for increased AP cost
3209 						ubStanceCost = (UINT8) GetAPsToChangeStance( pSoldier, ubBestStance );
3210 						if ( BestAttack.ubAPCost + ubStanceCost > pSoldier->bActionPoints )
3211 						{
3212 							// AP cost would balance (plus X, minus X) but aim time is reduced
3213 							BestAttack.ubAimTime -= (BestAttack.ubAimTime - ubStanceCost);
3214 						}
3215 						else
3216 						{
3217 							BestAttack.ubAPCost += GetAPsToChangeStance( pSoldier, ubBestStance );
3218 						}
3219 
3220 					}
3221 				}
3222 			}
3223 
3224 			//////////////////////////////////////////////////////////////////////////
3225 			// IF ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO
3226 			//////////////////////////////////////////////////////////////////////////
3227 
3228 			if (IsGunBurstCapable(pSoldier, BestAttack.bWeaponIn) &&
3229 				BestShot.opponent->bLife >= OKLIFE && // don't burst at downed targets
3230 				pSoldier->inv[BestAttack.bWeaponIn].ubGunShotsLeft > 1 &&
3231 				pSoldier->bTeam != OUR_TEAM)
3232 			{
3233 				UINT8 const ubBurstAPs = CalcAPsToBurst(CalcActionPoints(pSoldier), pSoldier->inv[BestAttack.bWeaponIn]);
3234 				if (pSoldier->bActionPoints - (BestAttack.ubAPCost - BestAttack.ubAimTime) >= ubBurstAPs )
3235 				{
3236 					// Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4
3237 					if ( TANK( pSoldier ) )
3238 					{
3239 						iChance = 100;
3240 					}
3241 					else
3242 					{
3243 						iChance = (25 / (BestAttack.ubAimTime + 1));
3244 						switch (pSoldier->bAttitude)
3245 						{
3246 							case DEFENSIVE:		iChance += -5; break;
3247 							case BRAVESOLO:		iChance +=  5; break;
3248 							case BRAVEAID:		iChance +=  5; break;
3249 							case CUNNINGSOLO:	iChance +=  0; break;
3250 							case CUNNINGAID:	iChance +=  0; break;
3251 							case AGGRESSIVE:	iChance += 10; break;
3252 							case ATTACKSLAYONLY:iChance += 30; break;
3253 						}
3254 						// increase chance based on proximity and difficulty of enemy
3255 						if ( PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) < 10 )
3256 						{
3257 							iChance += ( 10 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) );
3258 							if ( pSoldier->bAttitude == ATTACKSLAYONLY )
3259 							{
3260 								// increase it more!
3261 								iChance += 5 * ( 10 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) );
3262 							}
3263 						}
3264 					}
3265 
3266 					if ( (INT32) PreRandom( 100 ) < iChance)
3267 					{
3268 						BestAttack.ubAimTime = BURSTING;
3269 						BestAttack.ubAPCost = BestAttack.ubAPCost - BestAttack.ubAimTime + CalcAPsToBurst(CalcActionPoints(pSoldier), pSoldier->inv[HANDPOS]);
3270 						// check for spread burst possibilities
3271 						if (pSoldier->bAttitude != ATTACKSLAYONLY)
3272 						{
3273 							CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel );
3274 						}
3275 					}
3276 				}
3277 			}
3278 
3279 			//////////////////////////////////////////////////////////////////////////
3280 			// IF NOT CROUCHED & WILL STILL HAVE ENOUGH APs TO DO THIS SAME BEST
3281 			// ATTACK AFTER A STANCE CHANGE, CONSIDER CHANGING STANCE
3282 			//////////////////////////////////////////////////////////////////////////
3283 
3284 			if (BestAttack.ubAimTime == BURSTING)
3285 			{
3286 				pSoldier->bAimTime			= 0;
3287 				pSoldier->bDoBurst			= 1;
3288 			}
3289 
3290 			/*
3291 			else // defaults already set
3292 			{
3293 				pSoldier->bAimTime			= BestAttack.ubAimTime;
3294 				pSoldier->bDoBurst			= 0;
3295 			}
3296 			*/
3297 
3298 		}
3299 		else if (ubBestAttackAction == AI_ACTION_THROW_KNIFE)
3300 		{
3301 			if (BestAttack.bWeaponIn != HANDPOS && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
3302 			{
3303 				// we had better make sure we lower our gun first!
3304 
3305 				pSoldier->bAction = AI_ACTION_LOWER_GUN;
3306 				pSoldier->usActionData = 0;
3307 
3308 				// queue up attack for after we lower weapon if any
3309 				pSoldier->bNextAction = AI_ACTION_THROW_KNIFE;
3310 				pSoldier->usNextActionData = BestAttack.sTarget;
3311 				pSoldier->bNextTargetLevel = BestAttack.bTargetLevel;
3312 			}
3313 
3314 		}
3315 
3316 		//////////////////////////////////////////////////////////////////////////
3317 		// OTHERWISE, JUST GO AHEAD & ATTACK!
3318 		//////////////////////////////////////////////////////////////////////////
3319 
3320 		// swap weapon to hand if necessary
3321 		if (BestAttack.bWeaponIn != HANDPOS)
3322 		{
3323 			RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER);
3324 		}
3325 
3326 		if (fChangeStanceFirst)
3327 		{ // currently only for guns...
3328 			pSoldier->bNextAction = AI_ACTION_FIRE_GUN;
3329 			pSoldier->usNextActionData = BestAttack.sTarget;
3330 			pSoldier->bNextTargetLevel = BestAttack.bTargetLevel;
3331 			pSoldier->usActionData = ubBestStance;
3332 			return( AI_ACTION_CHANGE_STANCE );
3333 		}
3334 		else
3335 		{
3336 
3337 			pSoldier->usActionData = BestAttack.sTarget;
3338 			pSoldier->bTargetLevel = BestAttack.bTargetLevel;
3339 
3340 			SLOGD(ST::format("{}({}) {} {}({})",
3341 				pSoldier->ubID, pSoldier->name,
3342 				ubBestAttackAction == AI_ACTION_FIRE_GUN ? "SHOOTS" : (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE ? "TOSSES AT" : "STABS"),
3343 				BestAttack.opponent->ubID, BestAttack.opponent->name));
3344 			return(ubBestAttackAction);
3345 		}
3346 
3347 	}
3348 
3349 	// end of tank AI
3350 	if ( TANK( pSoldier ) )
3351 	{
3352 		return( AI_ACTION_NONE );
3353 	}
3354 
3355 	// try to make boxer close if possible
3356 	if (pSoldier->uiStatusFlags & SOLDIER_BOXER )
3357 	{
3358 		if ( ubCanMove )
3359 		{
3360 			sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
3361 			if ( sClosestOpponent != NOWHERE )
3362 			{
3363 				// temporarily make boxer have orders of CLOSEPATROL rather than STATIONARY
3364 				// so he has a good roaming range
3365 				pSoldier->bOrders = CLOSEPATROL;
3366 				pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, AI_ACTION_GET_CLOSER );
3367 				pSoldier->bOrders = STATIONARY;
3368 				if ( pSoldier->usActionData != NOWHERE )
3369 				{
3370 					// truncate path to 1 step
3371 					pSoldier->usActionData = pSoldier->sGridNo + DirectionInc( pSoldier->ubPathingData[0] );
3372 					pSoldier->sFinalDestination = pSoldier->usActionData;
3373 					pSoldier->bNextAction = AI_ACTION_END_TURN;
3374 					return( AI_ACTION_GET_CLOSER );
3375 				}
3376 			}
3377 		}
3378 		// otherwise do nothing
3379 		return( AI_ACTION_NONE );
3380 	}
3381 
3382 	////////////////////////////////////////////////////////////////////////////
3383 	// IF A LOCATION WITH BETTER COVER IS AVAILABLE & REACHABLE, GO FOR IT!
3384 	////////////////////////////////////////////////////////////////////////////
3385 
3386 	if (sBestCover != NOWHERE)
3387 	{
3388 		SLOGD(ST::format("{} - taking cover at gridno {} ({}% better)",
3389 			pSoldier->name, sBestCover, iCoverPercentBetter));
3390 		pSoldier->usActionData = sBestCover;
3391 		return(AI_ACTION_TAKE_COVER);
3392 	}
3393 
3394 
3395 	////////////////////////////////////////////////////////////////////////////
3396 	// IF THINGS ARE REALLY HOPELESS, OR UNARMED, TRY TO RUN AWAY
3397 	////////////////////////////////////////////////////////////////////////////
3398 
3399 	// if soldier has enough APs left to move at least 1 square's worth
3400 	if ( ubCanMove && (pSoldier->bTeam != OUR_TEAM || pSoldier->fAIFlags & AI_RTP_OPTION_CAN_RETREAT) )
3401 	{
3402 		if ((pSoldier->bAIMorale == MORALE_HOPELESS) || !bCanAttack)
3403 		{
3404 			// look for best place to RUN AWAY to (farthest from the closest threat)
3405 			pSoldier->usActionData = FindSpotMaxDistFromOpponents(pSoldier);
3406 
3407 			if (pSoldier->usActionData != NOWHERE)
3408 			{
3409 				return(AI_ACTION_RUN_AWAY);
3410 			}
3411 		}
3412 	}
3413 
3414 	////////////////////////////////////////////////////////////////////////////
3415 	// IF SPOTTERS HAVE BEEN CALLED FOR, AND WE HAVE SOME NEW SIGHTINGS, RADIO!
3416 	////////////////////////////////////////////////////////////////////////////
3417 
3418 	// if we're a computer merc, and we have the action points remaining to RADIO
3419 	// (we never want NPCs to choose to radio if they would have to wait a turn)
3420 	// and we're not swimming in deep water, and somebody has called for spotters
3421 	// and we see the location of at least 2 opponents
3422 	if ((gTacticalStatus.ubSpottersCalledForBy != NOBODY) && (pSoldier->bActionPoints >= AP_RADIO) &&
3423 		(pSoldier->bOppCnt > 1) && !fCivilian &&
3424 		(gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) && !bInDeepWater)
3425 	{
3426 		// base chance depends on how much new info we have to radio to the others
3427 		iChance = 25 * WhatIKnowThatPublicDont(pSoldier,TRUE);	// just count them
3428 
3429 		// if I actually know something they don't
3430 		if (iChance)
3431 		{
3432 			if ((INT16)PreRandom(100) < iChance)
3433 			{
3434 				return(AI_ACTION_RED_ALERT);
3435 			}
3436 		}
3437 	}
3438 
3439 
3440 	////////////////////////////////////////////////////////////////////////////
3441 	// CROUCH IF NOT CROUCHING ALREADY
3442 	////////////////////////////////////////////////////////////////////////////
3443 
3444 	// if not in water and not already crouched, try to crouch down first
3445 	if (!gfTurnBasedAI || GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) <= pSoldier->bActionPoints)
3446 	{
3447 		if ( !fCivilian && !gfHiddenInterrupt && IsValidStance( pSoldier, ANIM_CROUCH ) )
3448 		{
3449 			pSoldier->usActionData = StanceChange( pSoldier, BestAttack.ubAPCost );
3450 			if (pSoldier->usActionData != 0)
3451 			{
3452 				if (pSoldier->usActionData == ANIM_PRONE)
3453 				{
3454 					// we might want to turn before lying down!
3455 					if ( (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints - GetAPsToChangeStance( pSoldier, (INT8) pSoldier->usActionData )) &&
3456 						(pSoldier->bAIMorale > MORALE_HOPELESS || ubCanMove))
3457 					{
3458 						// determine the location of the known closest opponent
3459 						// (don't care if he's conscious, don't care if he's reachable at all)
3460 
3461 						sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
3462 						// if we have a closest seen opponent
3463 						if (sClosestOpponent != NOWHERE)
3464 						{
3465 							const INT8 bDirection = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestOpponent);
3466 
3467 							// if we're not facing towards him
3468 							if (pSoldier->bDirection != bDirection)
3469 							{
3470 								if ( InternalIsValidStance( pSoldier, bDirection, (INT8) pSoldier->usActionData) )
3471 								{
3472 									// change direction, THEN change stance!
3473 									pSoldier->bNextAction = AI_ACTION_CHANGE_STANCE;
3474 									pSoldier->usNextActionData = pSoldier->usActionData;
3475 									pSoldier->usActionData = bDirection;
3476 									return(AI_ACTION_CHANGE_FACING);
3477 								}
3478 								else if ( (pSoldier->usActionData == ANIM_PRONE) && (InternalIsValidStance( pSoldier, bDirection, ANIM_CROUCH) ) )
3479 								{
3480 									// we shouldn't go prone, since we can't turn to shoot
3481 									pSoldier->usActionData = ANIM_CROUCH;
3482 									pSoldier->bNextAction = AI_ACTION_END_TURN;
3483 									return( AI_ACTION_CHANGE_STANCE );
3484 								}
3485 							}
3486 							// else we are facing in the right direction
3487 
3488 						}
3489 						// else we don't know any enemies
3490 					}
3491 
3492 					// we don't want to turn
3493 				}
3494 				pSoldier->bNextAction = AI_ACTION_END_TURN;
3495 				return( AI_ACTION_CHANGE_STANCE );
3496 			}
3497 		}
3498 	}
3499 
3500 	////////////////////////////////////////////////////////////////////////////
3501 	// TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY)
3502 	////////////////////////////////////////////////////////////////////////////
3503 
3504 	if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints)
3505 	{
3506 		// hopeless guys shouldn't waste their time this way, UNLESS they CAN move
3507 		// but chose not to to get this far (which probably means they're cornered)
3508 		if (!gfHiddenInterrupt && (pSoldier->bAIMorale > MORALE_HOPELESS || ubCanMove))
3509 		{
3510 			// determine the location of the known closest opponent
3511 			// (don't care if he's conscious, don't care if he's reachable at all)
3512 
3513 
3514 			sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL);
3515 			// if we have a closest reachable opponent
3516 			if (sClosestOpponent != NOWHERE)
3517 			{
3518 				const INT8 bDirection = GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sClosestOpponent);
3519 
3520 				// if we're not facing towards him
3521 				if ( pSoldier->bDirection != bDirection && InternalIsValidStance( pSoldier, bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
3522 				{
3523 					pSoldier->usActionData = bDirection;
3524 					return(AI_ACTION_CHANGE_FACING);
3525 				}
3526 			}
3527 		}
3528 	}
3529 
3530 	// if a militia has absofreaking nothing else to do, maybe they should radio in a report!
3531 
3532 	////////////////////////////////////////////////////////////////////////////
3533 	// RADIO RED ALERT: determine %chance to call others and report contact
3534 	////////////////////////////////////////////////////////////////////////////
3535 
3536 	if ( pSoldier->bTeam == MILITIA_TEAM && (pSoldier->bActionPoints >= AP_RADIO) && (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) )
3537 	{
3538 
3539 		// if there hasn't been an initial RED ALERT yet in this sector
3540 		if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger() )
3541 			// since I'm at STATUS RED, I obviously know we're being invaded!
3542 			iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ];
3543 		else // subsequent radioing (only to update enemy positions, request help)
3544 			// base chance depends on how much new info we have to radio to the others
3545 			iChance = 10 * WhatIKnowThatPublicDont(pSoldier,FALSE);  // use 10 * for RED alert
3546 
3547 		// if I actually know something they don't and I ain't swimming (deep water)
3548 		if (iChance && !bInDeepWater)
3549 		{
3550 			// modify base chance according to orders
3551 			switch (pSoldier->bOrders)
3552 			{
3553 				case STATIONARY:       iChance +=  20;  break;
3554 				case ONGUARD:          iChance +=  15;  break;
3555 				case ONCALL:           iChance +=  10;  break;
3556 				case CLOSEPATROL:                       break;
3557 				case RNDPTPATROL:
3558 				case POINTPATROL:      iChance +=  -5;  break;
3559 				case FARPATROL:        iChance += -10;  break;
3560 				case SEEKENEMY:        iChance += -20;  break;
3561 			}
3562 
3563 			// modify base chance according to attitude
3564 			switch (pSoldier->bAttitude)
3565 			{
3566 				case DEFENSIVE:        iChance +=  20;  break;
3567 				case BRAVESOLO:        iChance += -10;  break;
3568 				case BRAVEAID:                          break;
3569 				case CUNNINGSOLO:      iChance +=  -5;  break;
3570 				case CUNNINGAID:                        break;
3571 				case AGGRESSIVE:       iChance += -20;  break;
3572 				case ATTACKSLAYONLY:		iChance = 0;
3573 			}
3574 
3575 			if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition)
3576 			{
3577 				// ignore morale (which could be really high)
3578 			}
3579 			else
3580 			{
3581 				// modify base chance according to morale
3582 				switch (pSoldier->bAIMorale)
3583 				{
3584 					case MORALE_HOPELESS:  iChance *= 3;    break;
3585 					case MORALE_WORRIED:   iChance *= 2;    break;
3586 					case MORALE_NORMAL:                     break;
3587 					case MORALE_CONFIDENT: iChance /= 2;    break;
3588 					case MORALE_FEARLESS:  iChance /= 3;    break;
3589 				}
3590 			}
3591 
3592 			// reduce chance because we're in combat
3593 			iChance /= 2;
3594 
3595 			if ((INT16) PreRandom(100) < iChance)
3596 			{
3597 				return(AI_ACTION_RED_ALERT);
3598 			}
3599 		}
3600 	}
3601 
3602 	////////////////////////////////////////////////////////////////////////////
3603 	// LEAVE THE SECTOR
3604 	////////////////////////////////////////////////////////////////////////////
3605 
3606 	// NOT IMPLEMENTED
3607 
3608 	////////////////////////////////////////////////////////////////////////////
3609 	// DO NOTHING: Not enough points left to move, so save them for next turn
3610 	////////////////////////////////////////////////////////////////////////////
3611 	// by default, if everything else fails, just stand in place and wait
3612 	pSoldier->usActionData = NOWHERE;
3613 	return(AI_ACTION_NONE);
3614 }
3615 
DecideAction(SOLDIERTYPE * pSoldier)3616 INT8 DecideAction(SOLDIERTYPE *pSoldier)
3617 {
3618 	INT8 bAction = AI_ACTION_NONE;
3619 
3620 #ifdef AI_TIMING_TESTS
3621 	UINT32 uiStartTime, uiEndTime;
3622 #endif
3623 
3624 	// turn off cautious flag
3625 	pSoldier->fAIFlags &= (~AI_CAUTIOUS);
3626 
3627 	// if status over-ride is set, bypass RED/YELLOW and go directly to GREEN!
3628 	if ((pSoldier->bBypassToGreen) && (pSoldier->bAlertStatus < STATUS_BLACK))
3629 	{
3630 		bAction = DecideActionGreen(pSoldier);
3631 		if (!gfTurnBasedAI)
3632 		{
3633 			// reset bypass now
3634 			pSoldier->bBypassToGreen = 0;
3635 		}
3636 	}
3637 	else
3638 	{
3639 		switch (pSoldier->bAlertStatus)
3640 		{
3641 			case STATUS_GREEN:
3642 #ifdef AI_TIMING_TESTS
3643 				uiStartTime = GetJA2Clock();
3644 #endif
3645 				bAction = DecideActionGreen(pSoldier);
3646 #ifdef AI_TIMING_TESTS
3647 				uiEndTime = GetJA2Clock();
3648 				guiGreenTimeTotal += (uiEndTime - uiStartTime);
3649 				guiGreenCounter++;
3650 #endif
3651 				break;
3652 
3653 			case STATUS_YELLOW:
3654 #ifdef AI_TIMING_TESTS
3655 				uiStartTime = GetJA2Clock();
3656 #endif
3657 				bAction = DecideActionYellow(pSoldier);
3658 #ifdef AI_TIMING_TESTS
3659 				uiEndTime = GetJA2Clock();
3660 				guiYellowTimeTotal += (uiEndTime - uiStartTime);
3661 				guiYellowCounter++;
3662 #endif
3663 				break;
3664 
3665 			case STATUS_RED:
3666 #ifdef AI_TIMING_TESTS
3667 				uiStartTime = GetJA2Clock();
3668 #endif
3669 				bAction = DecideActionRed(pSoldier,TRUE);
3670 #ifdef AI_TIMING_TESTS
3671 				uiEndTime = GetJA2Clock();
3672 				guiRedTimeTotal += (uiEndTime - uiStartTime);
3673 				guiRedCounter++;
3674 #endif
3675 				break;
3676 
3677 			case STATUS_BLACK:
3678 #ifdef AI_TIMING_TESTS
3679 				uiStartTime = GetJA2Clock();
3680 #endif
3681 				bAction = DecideActionBlack(pSoldier);
3682 #ifdef AI_TIMING_TESTS
3683 				uiEndTime = GetJA2Clock();
3684 				guiBlackTimeTotal += (uiEndTime - uiStartTime);
3685 				guiBlackCounter++;
3686 #endif
3687 				break;
3688 		}
3689 	}
3690 	SLOGD(ST::format("DecideAction: selected action {}, actionData {}\n\n",
3691 		bAction, pSoldier->usActionData));
3692 	return(bAction);
3693 }
3694 
3695 
DecideAlertStatus(SOLDIERTYPE * pSoldier)3696 void DecideAlertStatus( SOLDIERTYPE *pSoldier )
3697 {
3698 	INT8	bOldStatus;
3699 	INT32	iDummy;
3700 	BOOLEAN fClimbDummy,fReachableDummy;
3701 
3702 	// THE FOUR (4) POSSIBLE ALERT STATUSES ARE:
3703 	// GREEN - No one seen, no suspicious noise heard, go about regular duties
3704 	// YELLOW - Suspicious noise was heard personally or radioed in by buddy
3705 	// RED - Either saw opponents in person, or definite contact had been radioed
3706 	// BLACK - Currently has one or more opponents in sight
3707 
3708 	// save the man's previous status
3709 
3710 	if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
3711 	{
3712 		CreatureDecideAlertStatus( pSoldier );
3713 		return;
3714 	}
3715 
3716 	bOldStatus = pSoldier->bAlertStatus;
3717 
3718 	// determine the current alert status for this category of man
3719 	//if (!(pSoldier->uiStatusFlags & SOLDIER_PC))
3720 	{
3721 		if (pSoldier->bOppCnt > 0)        // opponent(s) in sight
3722 		{
3723 			pSoldier->bAlertStatus = STATUS_BLACK;
3724 			CheckForChangingOrders( pSoldier );
3725 		}
3726 		else                        // no opponents are in sight
3727 		{
3728 			switch (bOldStatus)
3729 			{
3730 				case STATUS_BLACK:
3731 					// then drop back to RED status
3732 					pSoldier->bAlertStatus = STATUS_RED;
3733 					break;
3734 
3735 				case STATUS_RED:
3736 					// RED can never go back down below RED, only up to BLACK
3737 					break;
3738 
3739 				case STATUS_YELLOW:
3740 					// if all enemies have been RED alerted, or we're under fire
3741 					if (!IsOnCivTeam(pSoldier) &&
3742 						(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire))
3743 					{
3744 						pSoldier->bAlertStatus = STATUS_RED;
3745 					}
3746 					else
3747 					{
3748 						// if we are NOT aware of any uninvestigated noises right now
3749 						// and we are not currently in the middle of an action
3750 						// (could still be on his way heading to investigate a noise!)
3751 						if ((MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE) && !pSoldier->bActionInProgress)
3752 						{
3753 							// then drop back to GREEN status
3754 							pSoldier->bAlertStatus = STATUS_GREEN;
3755 							CheckForChangingOrders( pSoldier );
3756 						}
3757 					}
3758 					break;
3759 
3760 				case STATUS_GREEN:
3761 					// if all enemies have been RED alerted, or we're under fire
3762 					if (!IsOnCivTeam(pSoldier) &&
3763 						(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->bUnderFire))
3764 					{
3765 						pSoldier->bAlertStatus = STATUS_RED;
3766 					}
3767 					else
3768 					{
3769 						// if we ARE aware of any uninvestigated noises right now
3770 						if (MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) != NOWHERE)
3771 						{
3772 							// then move up to YELLOW status
3773 							pSoldier->bAlertStatus = STATUS_YELLOW;
3774 						}
3775 					}
3776 					break;
3777 			}
3778 			// otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN
3779 		}
3780 	}
3781 
3782 #if 0
3783 	else
3784 	{
3785 		if (pSoldier->bOppCnt > 0)
3786 		{
3787 			pSoldier->bAlertStatus = STATUS_BLACK; // opponent(s) in sight
3788 		}
3789 		else
3790 		{
3791 			pSoldier->bAlertStatus = STATUS_RED;         // enemy sector
3792 		}
3793 	}
3794 #endif
3795 
3796 	if ( gTacticalStatus.bBoxingState == NOT_BOXING )
3797 	{
3798 
3799 		// if the man's alert status has changed in any way
3800 		if (pSoldier->bAlertStatus != bOldStatus)
3801 		{
3802 			// HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK
3803 			// if either status is < RED (ie. anything but RED->BLACK && BLACK->RED)
3804 			if ((bOldStatus < STATUS_RED) || (pSoldier->bAlertStatus < STATUS_RED))
3805 			{
3806 				// force a NEW action decision on next pass through HandleManAI()
3807 				SetNewSituation( pSoldier );
3808 			}
3809 
3810 			// if this guy JUST discovered that there were opponents here for sure...
3811 			if ((bOldStatus < STATUS_RED) && (pSoldier->bAlertStatus >= STATUS_RED))
3812 			{
3813 				CheckForChangingOrders(pSoldier);
3814 			}
3815 		}
3816 		else   // status didn't change
3817 		{
3818 			// only do this stuff in TB
3819 			// if a guy on status GREEN or YELLOW is running low on breath
3820 			if (((pSoldier->bAlertStatus == STATUS_GREEN)  && (pSoldier->bBreath < 75)) ||
3821 				((pSoldier->bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50)))
3822 			{
3823 				// as long as he's not in water (standing on a bridge is OK)
3824 				if (!MercInWater(pSoldier))
3825 				{
3826 					// force a NEW decision so that he can get some rest
3827 					SetNewSituation( pSoldier );
3828 
3829 					// current action will be canceled. if noise is no longer important
3830 					if ((pSoldier->bAlertStatus == STATUS_YELLOW) &&
3831 						(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy) == NOWHERE))
3832 					{
3833 						// then drop back to GREEN status
3834 						pSoldier->bAlertStatus = STATUS_GREEN;
3835 						CheckForChangingOrders( pSoldier );
3836 					}
3837 				}
3838 			}
3839 		}
3840 	}
3841 
3842 }
3843