1 #include "Handle_Doors.h"
2 #include "Structure.h"
3 #include "Timer_Control.h"
4 #include "AI.h"
5 #include "Isometric_Utils.h"
6 #include "Overhead.h"
7 #include "Overhead_Types.h"
8 #include "OppList.h"
9 #include "Animation_Control.h"
10 #include "Font_Control.h"
11 #include "Interface.h"
12 #include "PathAI.h"
13 #include "Points.h"
14 #include "Weapons.h"
15 #include "Items.h"
16 #include "Handle_Items.h"
17 #include "AIInternals.h"
18 #include "Animation_Data.h"
19 #include "LOS.h"
20 #include "Message.h"
21 #include "TeamTurns.h"
22 #include "NPC.h"
23 #include "Dialogue_Control.h"
24 #include "Soldier_Profile.h"
25 #include "StrategicMap.h"
26 #include "Soldier_Create.h"
27 #include "Explosion_Control.h"
28 #include "Interactive_Tiles.h"
29 #include "Interface_Dialogue.h"
30 #include "Vehicles.h"
31 #include "RenderWorld.h"
32 #include "AIList.h"
33 #include "Soldier_Macros.h"
34 #include "Bullets.h"
35 #include "Physics.h"
36 #include "Interface_Panels.h"
37 #include "Sound_Control.h"
38 #include "Civ_Quotes.h"
39 #include "Quests.h"
40 #include "Queen_Command.h"
41 #include "Debug.h"
42 
43 #include <algorithm>
44 
45 #define AI_DELAY 100
46 
47 
48 //
49 // Commented out/ to fix:
50 // lots of other stuff, I think
51 //
52 
53 #define DEADLOCK_DELAY							15000
54 #define AI_LIMIT_PER_UPDATE		1
55 
56 BOOLEAN gfTurnBasedAI;
57 
58 INT8 gbDiff[MAX_DIFF_PARMS][5] =
59 {
60 	//       AI DIFFICULTY SETTING
61 	// WIMPY  EASY  NORMAL  TOUGH  ELITE
62 	{  -20,  -10,     0,    10,     20  },     // DIFF_ENEMY_EQUIP_MOD
63 	{  -10,   -5,     0,     5,     10  },     // DIFF_ENEMY_TO_HIT_MOD
64 	{   -2,   -1,     0,     1,      2  },     // DIFF_ENEMY_INTERRUPT_MOD
65 	{   50,   65,    80,    90,     95  },     // DIFF_RADIO_RED_ALERT
66 	{    4,    6,     8,    10,     13  }      // DIFF_MAX_COVER_RANGE
67 };
68 
InitAI(void)69 void InitAI(void)
70 {
71 #ifdef _DEBUG
72 	if (gfDisplayCoverValues)
73 	{
74 		std::fill_n(gsCoverValue, WORLD_MAX, 0x7F7F);
75 	}
76 #endif
77 
78 	//If we are not loading a saved game ( if we are, this has already been called )
79 	if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
80 	{
81 		//init the panic system
82 		InitPanicSystem();
83 	}
84 }
85 
86 
87 static void HandleAITacticalTraversal(SOLDIERTYPE&);
88 static void TurnBasedHandleNPCAI(SOLDIERTYPE* pSoldier);
89 
90 
HandleSoldierAI(SOLDIERTYPE * pSoldier)91 void HandleSoldierAI( SOLDIERTYPE *pSoldier )
92 {
93 	// ATE
94 	// Bail if we are engaged in a NPC conversation/ and/or sequence ... or we have a pause because
95 	// we just saw someone... or if there are bombs on the bomb queue
96 	if ( pSoldier->uiStatusFlags & SOLDIER_ENGAGEDINACTION || gTacticalStatus.fEnemySightingOnTheirTurn || (gubElementsOnExplosionQueue != 0) )
97 	{
98 		return;
99 	}
100 
101 	if ( gfExplosionQueueActive )
102 	{
103 		return;
104 	}
105 
106 	if (pSoldier->uiStatusFlags & SOLDIER_PC)
107 	{
108 		// if we're in autobandage, or the AI control flag is set and the player has a quote record to perform, or is a boxer,
109 		// let AI process this merc; otherwise abort
110 		if ( !(gTacticalStatus.fAutoBandageMode) && !(pSoldier->uiStatusFlags & SOLDIER_PCUNDERAICONTROL && (pSoldier->ubQuoteRecord != 0 || pSoldier->uiStatusFlags & SOLDIER_BOXER) ) )
111 		{
112 			// patch...
113 			if ( pSoldier->fAIFlags & AI_HANDLE_EVERY_FRAME )
114 			{
115 				pSoldier->fAIFlags &= ~AI_HANDLE_EVERY_FRAME;
116 			}
117 			return;
118 		}
119 
120 	}
121 
122 	// determine what sort of AI to use
123 	if (gTacticalStatus.uiFlags & INCOMBAT)
124 	{
125 		gfTurnBasedAI = TRUE;
126 	}
127 	else
128 	{
129 		gfTurnBasedAI = FALSE;
130 	}
131 
132 	// If TURN BASED and NOT NPC's turn, or realtime and not our chance to think, bail...
133 	if (gfTurnBasedAI)
134 	{
135 		if ( (pSoldier->bTeam != OUR_TEAM) && gTacticalStatus.ubCurrentTeam == OUR_TEAM )
136 		{
137 			return;
138 		}
139 		// why do we let the quote record thing be in here?  we're in turnbased the quote record doesn't matter,
140 		// we can't act out of turn!
141 		if ( !(pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL) )
142 		{
143 			return;
144 		}
145 
146 		if ( pSoldier->bTeam != gTacticalStatus.ubCurrentTeam )
147 		{
148 			SLOGE("Turning off AI flag for %d because trying to act out of turn", pSoldier->ubID );
149 			pSoldier->uiStatusFlags &= ~SOLDIER_UNDERAICONTROL;
150 			return;
151 		}
152 		if ( pSoldier->bMoved )
153 		{
154 			if (gfTurnBasedAI)
155 			{
156 				SLOGD("Ending turn for %d because set to moved", pSoldier->ubID);
157 			}
158 			// this guy doesn't get to act!
159 			EndAIGuysTurn(*pSoldier);
160 			return;
161 		}
162 
163 	}
164 	else if ( !(pSoldier->fAIFlags & AI_HANDLE_EVERY_FRAME) ) // if set to handle every frame, ignore delay!
165 	{
166 	//#ifndef AI_PROFILING
167 		//Time to handle guys in realtime (either combat or not )
168 		if (!TIMECOUNTERDONE(pSoldier->AICounter, AI_DELAY))
169 		{
170 			// CAMFIELD, LOOK HERE!
171 			return;
172 		}
173 		else
174 		{
175 			//Reset counter!
176 			RESETTIMECOUNTER(pSoldier->AICounter, AI_DELAY);
177 		}
178 		//#endif
179 	}
180 
181 	if ( pSoldier->fAIFlags & AI_HANDLE_EVERY_FRAME ) // if set to handle every frame, ignore delay!
182 	{
183 		if (pSoldier->ubQuoteActionID != QUOTE_ACTION_ID_TURNTOWARDSPLAYER)
184 		{
185 			// turn off flag!
186 			pSoldier->fAIFlags &= (~AI_HANDLE_EVERY_FRAME);
187 		}
188 	}
189 
190 	// if this NPC is getting hit, abort
191 	if (pSoldier->fGettingHit)
192 	{
193 		return;
194 	}
195 
196 	if ( gTacticalStatus.bBoxingState == PRE_BOXING || gTacticalStatus.bBoxingState == BOXING || gTacticalStatus.bBoxingState == WON_ROUND || gTacticalStatus.bBoxingState == LOST_ROUND )
197 	{
198 		if ( ! ( pSoldier->uiStatusFlags & SOLDIER_BOXER ) )
199 		{
200 			// do nothing!
201 			if (gfTurnBasedAI)
202 			{
203 				SLOGD("Ending turn for %d because not a boxer", pSoldier->ubID);
204 			}
205 			EndAIGuysTurn(*pSoldier);
206 			return;
207 		}
208 	}
209 
210 	// if this NPC is dying, bail
211 	if (pSoldier->bLife < OKLIFE || !pSoldier->bActive )
212 	{
213 		if ( pSoldier->bActive && pSoldier->fMuzzleFlash )
214 		{
215 			EndMuzzleFlash( pSoldier );
216 		}
217 		if (gfTurnBasedAI)
218 		{
219 			SLOGD("Ending turn for %d because bad life/inactive", pSoldier->ubID);
220 		}
221 
222 		EndAIGuysTurn(*pSoldier);
223 		return;
224 	}
225 
226 	if ( pSoldier->fAIFlags & AI_ASLEEP )
227 	{
228 		if ( gfTurnBasedAI && pSoldier->bVisible )
229 		{
230 			// turn off sleep flag, guy's got to be able to do stuff in turnbased
231 			// if he's visible
232 			pSoldier->fAIFlags &= ~AI_ASLEEP;
233 		}
234 		else if ( !(pSoldier->fAIFlags & AI_CHECK_SCHEDULE) )
235 		{
236 			// don't do anything!
237 			if (gfTurnBasedAI)
238 			{
239 				SLOGD("Ending turn for %d because asleep and no scheduled action", pSoldier->ubID);
240 			}
241 
242 			EndAIGuysTurn(*pSoldier);
243 			return;
244 		}
245 	}
246 
247 	if (!pSoldier->bInSector && !(pSoldier->fAIFlags & AI_CHECK_SCHEDULE))
248 	{
249 		// don't do anything!
250 		if (gfTurnBasedAI)
251 		{
252 			SLOGD("Ending turn for %d because out of sector and no scheduled action", pSoldier->ubID);
253 		}
254 
255 		EndAIGuysTurn(*pSoldier);
256 		return;
257 	}
258 
259 	if (IsMechanical(*pSoldier) && !TANK(pSoldier))
260 	{
261 		// bail out!
262 		if (gfTurnBasedAI)
263 		{
264 			SLOGD("Ending turn for %d because is vehicle or robot", pSoldier->ubID);
265 		}
266 
267 		EndAIGuysTurn(*pSoldier);
268 		return;
269 	}
270 
271 	if (pSoldier->bCollapsed)
272 	{
273 		// being handled so turn off muzzle flash
274 		if ( pSoldier->fMuzzleFlash )
275 		{
276 			EndMuzzleFlash( pSoldier );
277 		}
278 		if (gfTurnBasedAI)
279 		{
280 			SLOGD("Ending turn for %d because unconscious", pSoldier->ubID);
281 		}
282 
283 		// stunned/collapsed!
284 		CancelAIAction(pSoldier);
285 		EndAIGuysTurn(*pSoldier);
286 		return;
287 	}
288 
289 	// in the unlikely situation (Sgt Krott et al) that we have a quote trigger going on
290 	// during turnbased, don't do any AI
291 	if ( pSoldier->ubProfile != NO_PROFILE && (pSoldier->ubProfile == SERGEANT || pSoldier->ubProfile == MIKE || pSoldier->ubProfile == JOE) && (gTacticalStatus.uiFlags & INCOMBAT) && (gfInTalkPanel || gfWaitingForTriggerTimer || !DialogueQueueIsEmpty() ) )
292 	{
293 		return;
294 	}
295 
296 	// ATE: Did some changes here
297 	// DON'T rethink if we are determined to get somewhere....
298 	if ( pSoldier->bNewSituation == IS_NEW_SITUATION )
299 	{
300 		BOOLEAN fProcessNewSituation;
301 
302 		// if this happens during an attack then do nothing... wait for the A.B.C.
303 		// to be reduced to 0 first -- CJC December 13th
304 		if ( gTacticalStatus.ubAttackBusyCount > 0 )
305 		{
306 			fProcessNewSituation = FALSE;
307 			// HACK!!
308 			if ( pSoldier->bAction == AI_ACTION_FIRE_GUN )
309 			{
310 				if ( guiNumBullets == 0 )
311 				{
312 					// abort attack!
313 					SLOGD("Attack busy count lobotomized due to new situation for %d", pSoldier->ubID );
314 					//gTacticalStatus.ubAttackBusyCount = 0;
315 					fProcessNewSituation = TRUE;
316 				}
317 			}
318 			else if ( pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE )
319 			{
320 				if ( guiNumObjectSlots == 0 )
321 				{
322 					// abort attack!
323 					SLOGD("Attack busy count lobotomized due to new situation for %d", pSoldier->ubID);
324 					gTacticalStatus.ubAttackBusyCount = 0;
325 					fProcessNewSituation = TRUE;
326 				}
327 			}
328 		}
329 		else
330 		{
331 			fProcessNewSituation = TRUE;
332 		}
333 
334 		if ( fProcessNewSituation )
335 		{
336 			if ( (pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL) && pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH && !GridNoOnVisibleWorldTile( pSoldier->sGridNo ) )
337 			{
338 				// traversing offmap, ignore new situations
339 			}
340 			else if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode  )
341 			{
342 				// don't force, don't want escorted mercs reacting to new opponents, etc.
343 				// now we don't have AI controlled escorted mercs though - CJC
344 				CancelAIAction(pSoldier);
345 				// zap any next action too
346 				if ( pSoldier->bAction != AI_ACTION_END_COWER_AND_MOVE )
347 				{
348 					pSoldier->bNextAction = AI_ACTION_NONE;
349 				}
350 				DecideAlertStatus( pSoldier );
351 			}
352 			else
353 			{
354 				if ( pSoldier->ubQuoteRecord )
355 				{
356 					// make sure we're not using combat AI
357 					pSoldier->bAlertStatus = STATUS_GREEN;
358 				}
359 				pSoldier->bNewSituation = WAS_NEW_SITUATION;
360 			}
361 		}
362 	}
363 	else
364 	{
365 		// might have been in 'was' state; no longer so...
366 		pSoldier->bNewSituation = NOT_NEW_SITUATION;
367 	}
368 	SLOGD("handling AI for %d",pSoldier->ubID);
369 
370 	/*********
371 	Start of new overall AI system
372 	********/
373 
374 
375 
376 	if (gfTurnBasedAI)
377 	{
378 		if (GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart > DEADLOCK_DELAY)
379 		{
380 			// ATE: Display message that deadlock occured...
381 			SLOGI("Breaking Deadlock");
382 			// If we are in beta version, also report message!
383 			SLOGE("Aborting AI deadlock for %d. Please sent LOG file and SAVE.", pSoldier->ubID );
384 			// just abort
385 			EndAIDeadlock();
386 			if ( !(pSoldier->uiStatusFlags & SOLDIER_UNDERAICONTROL) )
387 			{
388 				return;
389 			}
390 		}
391 	}
392 
393 	if (pSoldier->bAction == AI_ACTION_NONE)
394 	{
395 		// being handled so turn off muzzle flash
396 		if ( pSoldier->fMuzzleFlash )
397 		{
398 			EndMuzzleFlash( pSoldier );
399 		}
400 
401 		// figure out what to do!
402 		if (gfTurnBasedAI)
403 		{
404 			if (pSoldier->fNoAPToFinishMove)
405 			{
406 				// well that move must have been cancelled because we're thinking now!
407 				//pSoldier->fNoAPToFinishMove = FALSE;
408 			}
409 			TurnBasedHandleNPCAI( pSoldier );
410 		}
411 		else
412 		{
413 			RTHandleAI( pSoldier );
414 		}
415 
416 	}
417 	else
418 	{
419 
420 		// an old action was in progress; continue it
421 		if (pSoldier->bAction >= FIRST_MOVEMENT_ACTION && pSoldier->bAction <= LAST_MOVEMENT_ACTION && !pSoldier->fDelayedMovement)
422 		{
423 			if (pSoldier->ubPathIndex == pSoldier->ubPathDataSize)
424 			{
425 				if (pSoldier->sAbsoluteFinalDestination != NOWHERE)
426 				{
427 					if ( !ACTING_ON_SCHEDULE( pSoldier ) &&  SpacesAway( pSoldier->sGridNo, pSoldier->sAbsoluteFinalDestination ) < 4 )
428 					{
429 						// This is close enough... reached final destination for NPC system move
430 						if ( pSoldier->sAbsoluteFinalDestination != pSoldier->sGridNo )
431 						{
432 							// update NPC records to replace our final dest with this location
433 							ReplaceLocationInNPCDataFromProfileID( pSoldier->ubProfile, pSoldier->sAbsoluteFinalDestination, pSoldier->sGridNo );
434 						}
435 						pSoldier->sAbsoluteFinalDestination = pSoldier->sGridNo;
436 						// change action data so that we consider this our final destination below
437 						pSoldier->usActionData = pSoldier->sGridNo;
438 					}
439 
440 					if ( pSoldier->sAbsoluteFinalDestination == pSoldier->sGridNo )
441 					{
442 						pSoldier->sAbsoluteFinalDestination = NOWHERE;
443 
444 						if ( !ACTING_ON_SCHEDULE( pSoldier ) && pSoldier->ubQuoteRecord && pSoldier->ubQuoteActionID == QUOTE_ACTION_ID_CHECKFORDEST )
445 						{
446 							NPCReachedDestination( pSoldier, FALSE );
447 							// wait just a little bit so the queue can be processed
448 							pSoldier->bNextAction = AI_ACTION_WAIT;
449 							pSoldier->usNextActionData = 500;
450 
451 						}
452 						else if (pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH)
453 						{
454 							HandleAITacticalTraversal(*pSoldier);
455 							return;
456 						}
457 					}
458 					else
459 					{
460 						// make sure this guy is handled next frame!
461 						pSoldier->uiStatusFlags |= AI_HANDLE_EVERY_FRAME;
462 					}
463 				}
464 				// for regular guys still have to check for leaving the map
465 				else if (pSoldier->ubQuoteActionID >= QUOTE_ACTION_ID_TRAVERSE_EAST && pSoldier->ubQuoteActionID <= QUOTE_ACTION_ID_TRAVERSE_NORTH)
466 				{
467 					HandleAITacticalTraversal(*pSoldier);
468 					return;
469 				}
470 
471 				// reached destination
472 				SLOGD("Opponent %d reaches dest - action done",pSoldier->ubID);
473 
474 				if ( pSoldier->sGridNo == pSoldier->sFinalDestination )
475 				{
476 					if ( pSoldier->bAction == AI_ACTION_MOVE_TO_CLIMB )
477 					{
478 						// successfully moved to roof!
479 
480 						// fake setting action to climb roof and see if we can afford this
481 						pSoldier->bAction = AI_ACTION_CLIMB_ROOF;
482 						if (IsActionAffordable(pSoldier))
483 						{
484 							// set action to none and next action to climb roof so we do that next
485 							pSoldier->bAction = AI_ACTION_NONE;
486 							pSoldier->bNextAction = AI_ACTION_CLIMB_ROOF;
487 						}
488 
489 					}
490 				}
491 
492 				ActionDone(pSoldier);
493 			}
494 
495 			//*** TRICK- TAKE INTO ACCOUNT PAUSED FOR NO TIME ( FOR NOW )
496 			if (pSoldier->fNoAPToFinishMove )
497 			{
498 				SoldierTriesToContinueAlongPath(pSoldier);
499 			}
500 			// ATE: Let's also test if we are in any stationary animation...
501 			else if ( ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_STATIONARY ) )
502 			{
503 				// ATE: Put some ( MORE ) refinements on here....
504 				// If we are trying to open door, or jump fence  don't continue until done...
505 				if ( !pSoldier->fContinueMoveAfterStanceChange && !pSoldier->bEndDoorOpenCode )
506 				{
507 					//ATE: just a few more.....
508 					// If we have ANY pending aninmation that is movement.....
509 					if ( pSoldier->usPendingAnimation != NO_PENDING_ANIMATION && ( gAnimControl[ pSoldier->usPendingAnimation ].uiFlags & ANIM_MOVING ) )
510 					{
511 						// Don't do anything, we're waiting on a pending animation....
512 					}
513 					else
514 					{
515 						// OK, we have a move to finish...
516 						SLOGD("going to try to continue path for %d", pSoldier->ubID);
517 						SoldierTriesToContinueAlongPath(pSoldier);
518 					}
519 				}
520 			}
521 		}
522 
523 	}
524 
525 	/*********
526 	End of new overall AI system
527 	********/
528 
529 }
530 
531 
EndAIGuysTurn(SOLDIERTYPE & s)532 void EndAIGuysTurn(SOLDIERTYPE& s)
533 {
534 
535 	if (!gfTurnBasedAI) return;
536 
537 	TacticalStatusType& ts = gTacticalStatus;
538 	if (ts.fSomeoneHit)
539 	{
540 		ts.fSomeoneHit = FALSE;
541 	}
542 	else
543 	{
544 		SayCloseCallQuotes();
545 	}
546 
547 	// If civ in civ group and hostile, try to change nearby guys to hostile
548 	if (s.ubCivilianGroup != NON_CIV_GROUP && !s.bNeutral)
549 	{
550 		if (!(s.uiStatusFlags & SOLDIER_BOXER) ||
551 			(ts.bBoxingState != PRE_BOXING && ts.bBoxingState != BOXING))
552 		{
553 			ProfileID const first_pid = CivilianGroupMembersChangeSidesWithinProximity(&s);
554 			if (first_pid != NO_PROFILE) TriggerFriendWithHostileQuote(first_pid);
555 		}
556 	}
557 
558 	if (ts.uiFlags & SHOW_ALL_ROOFS && ts.uiFlags & INCOMBAT)
559 	{
560 		ts.uiFlags &= ~SHOW_ALL_ROOFS;
561 		SetRenderFlags(RENDER_FLAG_FULL);
562 		InvalidateWorldRedundency();
563 	}
564 
565 	// End this NPC's control, move to next dude
566 	EndRadioLocator(&s);
567 	s.uiStatusFlags   &= ~SOLDIER_UNDERAICONTROL;
568 	s.fTurnInProgress  = FALSE;
569 	s.bMoved           = TRUE;
570 	s.bBypassToGreen   = FALSE;
571 
572 	SLOGD("Ending control for %d", s.ubID);
573 
574 	// Find the next AI guy
575 	if (SOLDIERTYPE* const s = RemoveFirstAIListEntry())
576 	{
577 		StartNPCAI(*s);
578 	}
579 	else
580 	{ // We are at the end, return control to next team
581 		SLOGD("Ending AI turn");
582 		EndAITurn();
583 	}
584 }
585 
586 
EndAIDeadlock()587 void EndAIDeadlock()
588 {
589 	// Escape enemy's turn
590 
591 	// Find enemy with problem and free him up
592 	FOR_EACH_SOLDIER(i)
593 	{
594 		SOLDIERTYPE& s = *i;
595 		if (!s.bInSector) continue;
596 		if (!(s.uiStatusFlags & SOLDIER_UNDERAICONTROL)) continue;
597 		CancelAIAction(&s);
598 		if (gfTurnBasedAI)
599 		{
600 			SLOGD("Ending turn for %d because breaking deadlock", s.ubID);
601 		}
602 
603 		STLOGD("Number of bullets in the air is {}", guiNumBullets);
604 		SLOGD("Setting attack busy count to 0 from deadlock break");
605 		gTacticalStatus.ubAttackBusyCount = 0;
606 
607 		EndAIGuysTurn(s);
608 		return;
609 	}
610 
611 	StartPlayerTeamTurn(TRUE, FALSE);
612 }
613 
614 
StartNPCAI(SOLDIERTYPE & s)615 void StartNPCAI(SOLDIERTYPE& s)
616 {
617 	SetSoldierAsUnderAiControl(&s);
618 
619 	s.fTurnInProgress      = TRUE;
620 	s.sLastTwoLocations[0] = NOWHERE;
621 	s.sLastTwoLocations[1] = NOWHERE;
622 
623 	RefreshAI(&s);
624 	SLOGD("Giving control to %d", s.ubID);
625 
626 	TacticalStatusType& ts = gTacticalStatus;
627 	ts.uiTimeSinceMercAIStart = GetJA2Clock();
628 
629 	/* important: if "fPausedAnimation" is TRUE, then we have to turn it off else
630 	 * HandleSoldierAI() will not be called! */
631 
632 	// Locate to soldier, if we are not in an interrupt situation.
633 	if ((ts.uiFlags & INCOMBAT) && gubOutOfTurnPersons == 0)
634 	{
635 		if ((!(s.uiStatusFlags & SOLDIER_VEHICLE) || GetNumberInVehicle(GetVehicle(s.bVehicleID)) != 0) &&
636 				((s.bVisible != -1 && s.bLife != 0) || ts.uiFlags & SHOW_ALL_MERCS))
637 		{
638 			// If we are on a roof, set flag for rendering.
639 			if (s.bLevel != 0 && ts.uiFlags & INCOMBAT)
640 			{
641 				ts.uiFlags |= SHOW_ALL_ROOFS;
642 				SetRenderFlags(RENDER_FLAG_FULL);
643 				InvalidateWorldRedundency();
644 			}
645 
646 			// Skip locator for green friendly militia.
647 			if (s.bTeam != MILITIA_TEAM || s.bSide != 0 || s.bAlertStatus != STATUS_GREEN)
648 			{
649 				LocateSoldier(&s, SETLOCATORFAST);
650 			}
651 		}
652 
653 		UpdateEnemyUIBar();
654 	}
655 	DecideAlertStatus(&s);
656 }
657 
658 
FreeUpNPCFromPendingAction(SOLDIERTYPE * pSoldier)659 void FreeUpNPCFromPendingAction( 	SOLDIERTYPE *pSoldier )
660 {
661 	if ( pSoldier )
662 	{
663 		if ( pSoldier->bAction == AI_ACTION_PENDING_ACTION
664 			|| pSoldier->bAction == AI_ACTION_OPEN_OR_CLOSE_DOOR
665 			|| pSoldier->bAction == AI_ACTION_CREATURE_CALL
666 			|| pSoldier->bAction == AI_ACTION_YELLOW_ALERT
667 			|| pSoldier->bAction == AI_ACTION_RED_ALERT
668 			|| pSoldier->bAction == AI_ACTION_UNLOCK_DOOR
669 			|| pSoldier->bAction == AI_ACTION_PULL_TRIGGER
670 			|| pSoldier->bAction == AI_ACTION_LOCK_DOOR	)
671 		{
672 			if ( pSoldier->ubProfile != NO_PROFILE )
673 			{
674 				if ( pSoldier->ubQuoteRecord == NPC_ACTION_KYLE_GETS_MONEY )
675 				{
676 					// Kyle after getting money
677 					pSoldier->ubQuoteRecord = 0;
678 					TriggerNPCRecord( KYLE, 11 );
679 				}
680 				else if (pSoldier->usAnimState == END_OPENSTRUCT)
681 				{
682 					TriggerNPCWithGivenApproach(pSoldier->ubProfile, APPROACH_DONE_OPEN_STRUCTURE);
683 				}
684 				else if (pSoldier->usAnimState == PICKUP_ITEM || pSoldier->usAnimState == ADJACENT_GET_ITEM )
685 				{
686 					TriggerNPCWithGivenApproach(pSoldier->ubProfile, APPROACH_DONE_GET_ITEM);
687 				}
688 			}
689 			ActionDone(pSoldier);
690 		}
691 	}
692 }
693 
694 
FreeUpNPCFromAttacking(SOLDIERTYPE * const pSoldier)695 void FreeUpNPCFromAttacking(SOLDIERTYPE* const pSoldier)
696 {
697 	ActionDone(pSoldier);
698 
699 	/*
700 	if (pSoldier->bActionInProgress)
701 	{
702 		SLOGD("FreeUpNPCFromAttacking for %d", pSoldier->ubID);
703 		if (pSoldier->bAction == AI_ACTION_FIRE_GUN)
704 		{
705 			if (pSoldier->bDoBurst)
706 			{
707 				if (pSoldier->bBulletsLeft == 0)
708 				{
709 					// now find the target and have them say "close call" quote if
710 					// applicable
711 					pTarget = WhoIsThere2(pSoldier->sTargetGridNo, pSoldier->bTargetLevel);
712 					if (pTarget && pTarget->bTeam == OUR_TEAM && pTarget->fCloseCall && pTarget->bShock == 0)
713 					{
714 						// say close call quote!
715 						TacticalCharacterDialogue( pTarget, QUOTE_CLOSE_CALL );
716 						pTarget->fCloseCall = FALSE;
717 					}
718 					ActionDone(pSoldier);
719 					pSoldier->bDoBurst = FALSE;
720 				}
721 			}
722 			else
723 			{
724 				pTarget = WhoIsThere2(pSoldier->sTargetGridNo, pSoldier->bTargetLevel);
725 				if (pTarget && pTarget->bTeam == OUR_TEAM && pTarget->fCloseCall && pTarget->bShock == 0)
726 				{
727 					// say close call quote!
728 					TacticalCharacterDialogue( pTarget, QUOTE_CLOSE_CALL );
729 					pTarget->fCloseCall = FALSE;
730 				}
731 				ActionDone(pSoldier);
732 			}
733 		}
734 		else if ((pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE) || (pSoldier->bAction == AI_ACTION_KNIFE_STAB))
735 		{
736 			ActionDone(pSoldier);
737 		}
738 	}
739 
740 	// DO WE NEED THIS???
741 	//pSoldier->sTarget = NOWHERE;
742 
743 	// This is here to speed up resolution of interrupts that have already been
744 	// delayed while AttackingPerson was still set (causing ChangeControl to
745 	// bail).  Without it, an interrupt would have to wait until next ani frame!
746 	//if (SwitchTo > -1)
747 	//  ChangeControl();
748 	*/
749 }
750 
751 
FreeUpNPCFromTurning(SOLDIERTYPE * pSoldier)752 void FreeUpNPCFromTurning(SOLDIERTYPE* pSoldier)
753 {
754 	// if NPC is in the process of changing facing, mark him as being done!
755 	if ((pSoldier->bAction == AI_ACTION_CHANGE_FACING) && pSoldier->bActionInProgress)
756 	{
757 		SLOGD("FreeUpNPCFromTurning: our action %d, desdir %d dir %d",
758 			pSoldier->bAction, pSoldier->bDesiredDirection, pSoldier->bDirection);
759 		ActionDone(pSoldier);
760 	}
761 }
762 
763 
FreeUpNPCFromStanceChange(SOLDIERTYPE * pSoldier)764 void FreeUpNPCFromStanceChange(SOLDIERTYPE *pSoldier )
765 {
766 	// are we/were we doing something?
767 	if (pSoldier->bActionInProgress)
768 	{
769 		// check and see if we were changing stance
770 		if (pSoldier->bAction == AI_ACTION_CHANGE_STANCE || pSoldier->bAction == AI_ACTION_COWER || pSoldier->bAction == AI_ACTION_STOP_COWERING)
771 		{
772 			// yes we were - are we finished?
773 			if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->usActionData )
774 			{
775 				// yes! Free us up to do other fun things
776 				ActionDone(pSoldier);
777 			}
778 		}
779 	}
780 }
781 
FreeUpNPCFromRoofClimb(SOLDIERTYPE * pSoldier)782 void FreeUpNPCFromRoofClimb(SOLDIERTYPE *pSoldier )
783 {
784 	// are we/were we doing something?
785 	if (pSoldier->bActionInProgress)
786 	{
787 		// check and see if we were climbing
788 		if (pSoldier->bAction == AI_ACTION_CLIMB_ROOF)
789 		{
790 			// yes! Free us up to do other fun things
791 			ActionDone(pSoldier);
792 		}
793 	}
794 }
795 
796 
797 
798 
ActionDone(SOLDIERTYPE * pSoldier)799 void ActionDone(SOLDIERTYPE *pSoldier)
800 {
801 	// if an action is currently selected
802 	if (pSoldier->bAction != AI_ACTION_NONE)
803 	{
804 		if (pSoldier->uiStatusFlags & SOLDIER_MONSTER)
805 		{
806 			SLOGD("Cancelling actiondone: our action %d, desdir %d dir %d",
807 				pSoldier->bAction, pSoldier->bDesiredDirection, pSoldier->bDirection);
808 		}
809 
810 		// If doing an attack, reset attack busy count and # of bullets
811 		//if ( gTacticalStatus.ubAttackBusyCount )
812 		//{
813 		//	gTacticalStatus.ubAttackBusyCount = 0;
814 		//	SLOGD("Setting attack busy count to 0 due to Action Done");
815 		//	pSoldier->bBulletsLeft = 0;
816 		//}
817 
818 		// cancel any turning & movement by making current settings desired ones
819 		pSoldier->sFinalDestination	= pSoldier->sGridNo;
820 
821 		if ( !pSoldier->fNoAPToFinishMove )
822 		{
823 			EVENT_StopMerc(pSoldier);
824 			AdjustNoAPToFinishMove( pSoldier, FALSE );
825 		}
826 
827 		// cancel current action
828 		pSoldier->bLastAction       = pSoldier->bAction;
829 		pSoldier->bAction           = AI_ACTION_NONE;
830 		pSoldier->usActionData      = NOWHERE;
831 		pSoldier->bActionInProgress = FALSE;
832 		pSoldier->fDelayedMovement  = FALSE;
833 
834 		/*
835 		if ( pSoldier->bLastAction == AI_ACTION_CHANGE_STANCE || pSoldier->bLastAction == AI_ACTION_COWER || pSoldier->bLastAction == AI_ACTION_STOP_COWERING )
836 		{
837 			SoldierGotoStationaryStance( pSoldier );
838 		}
839 		*/
840 
841 
842 		// make sure pathStored is not left TRUE by accident.
843 		// This is possible if we decide on an action that we have no points for
844 		// (but which set pathStored).  The action is retained until next turn,
845 		// although NewDest isn't called.  A newSit. could cancel it before then!
846 		pSoldier->bPathStored = FALSE;
847 	}
848 }
849 
850 
851 // ////////////////////////////////////////////////////////////////////////////////////
852 // ////////////////////////////////////////////////////////////////////////////////////
853 // ////////////////////////////////////////////////////////////////////////////////////
854 // ////////////////////////////////////////////////////////////////////////////////////
855 // ////////////////////////////////////////////////////////////////////////////////////
856 // ////////////////////////////////////////////////////////////////////////////////////
857 // ////////////////////////////////////////////////////////////////////////////////////
858 // ////////////////////////////////////////////////////////////////////////////////////
859 // ////////////////////////////////////////////////////////////////////////////////////
860 
861 //	O L D    D G    A I    C O D E
862 
863 // ////////////////////////////////////////////////////////////////////////////////////
864 // ////////////////////////////////////////////////////////////////////////////////////
865 // ////////////////////////////////////////////////////////////////////////////////////
866 // ////////////////////////////////////////////////////////////////////////////////////
867 // ////////////////////////////////////////////////////////////////////////////////////
868 // ////////////////////////////////////////////////////////////////////////////////////
869 // ////////////////////////////////////////////////////////////////////////////////////
870 // ////////////////////////////////////////////////////////////////////////////////////
871 // ////////////////////////////////////////////////////////////////////////////////////
872 
873 
874 // GLOBALS:
875 
876 UINT8 SkipCoverCheck = FALSE;
877 THREATTYPE Threat[MAXMERCS];
878 
879 
880 // threat percentage is based on the certainty of opponent knowledge:
881 // opplist value:        -4  -3  -2  -1 SEEN  1    2   3   4   5
882 int ThreatPercent[10] = { 20, 40, 60, 80, 25, 100, 90, 75, 60, 45 };
883 
884 
885 
NPCDoesAct(SOLDIERTYPE * pSoldier)886 void NPCDoesAct(SOLDIERTYPE *pSoldier)
887 {
888 	// if the action is visible and we're in a hidden turnbased mode, go to turnbased
889 	if (!(gTacticalStatus.uiFlags & INCOMBAT) &&
890 			(pSoldier->bAction == AI_ACTION_FIRE_GUN ||
891 			pSoldier->bAction == AI_ACTION_TOSS_PROJECTILE ||
892 			pSoldier->bAction == AI_ACTION_KNIFE_MOVE ||
893 			pSoldier->bAction == AI_ACTION_KNIFE_STAB ||
894 			pSoldier->bAction == AI_ACTION_THROW_KNIFE))
895 	{
896 		DisplayHiddenTurnbased( pSoldier );
897 	}
898 
899 	if (gfHiddenInterrupt)
900 	{
901 		DisplayHiddenInterrupt( pSoldier );
902 	}
903 	// *** IAN deleted lots of interrupt related code here to simplify JA2	development
904 
905 	// CJC Feb 18 99: make sure that soldier is not in the middle of a turn due to visual crap to make enemies
906 	// face and point their guns at us
907 	if ( pSoldier->bDesiredDirection != pSoldier->bDirection )
908 	{
909 		pSoldier->bDesiredDirection = pSoldier->bDirection;
910 	}
911 }
912 
913 
NPCDoesNothing(SOLDIERTYPE * pSoldier)914 static void NPCDoesNothing(SOLDIERTYPE* pSoldier)
915 {
916 	// NPC, for whatever reason, did/could not start an action, so end his turn
917 	//pSoldier->moved = TRUE;
918 
919 	if (gfTurnBasedAI)
920 	{
921 		SLOGD("Ending turn for %d because doing no-action", pSoldier->ubID);
922 	}
923 
924 	EndAIGuysTurn(*pSoldier);
925 
926 	// *** IAN deleted lots of interrupt related code here to simplify JA2	development
927 }
928 
929 
CancelAIAction(SOLDIERTYPE * const pSoldier)930 void CancelAIAction(SOLDIERTYPE* const pSoldier)
931 {
932 	if (SkipCoverCheck)
933 		SLOGD("CancelAIAction: SkipCoverCheck turned OFF");
934 
935 	// re-enable cover checking, something is new or something strange happened
936 	SkipCoverCheck = FALSE;
937 
938 	// turn off new situation flag to stop this from repeating all the time!
939 	if ( pSoldier->bNewSituation == IS_NEW_SITUATION )
940 	{
941 		pSoldier->bNewSituation = WAS_NEW_SITUATION;
942 	}
943 
944 	// turn off RED/YELLOW status "bypass to Green", to re-check all actions
945 	pSoldier->bBypassToGreen = FALSE;
946 
947 	ActionDone(pSoldier);
948 }
949 
950 
ActionInProgress(SOLDIERTYPE * pSoldier)951 INT16 ActionInProgress(SOLDIERTYPE *pSoldier)
952 {
953 	// if NPC has a desired destination, but isn't currently going there
954 	if ((pSoldier->sFinalDestination != NOWHERE) && (pSoldier->sDestination != pSoldier->sFinalDestination))
955 	{
956 		// return success (TRUE) if we successfully resume the movement
957 		return(TryToResumeMovement(pSoldier,pSoldier->sFinalDestination));
958 	}
959 
960 
961 	// this here should never happen, but it seems to (turns sometimes hang!)
962 	if ((pSoldier->bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->bDesiredDirection != pSoldier->usActionData))
963 	{
964 		SLOGD("ActionInProgress: WARNING - CONTINUING FACING CHANGE...");
965 
966 		// don't try to pay any more APs for this, it was paid for once already!
967 		pSoldier->bDesiredDirection = (INT8) pSoldier->usActionData;   // turn to face direction in actionData
968 		return(TRUE);
969 	}
970 
971 
972 	// needs more time to complete action
973 	return(TRUE);
974 }
975 
976 
TurnBasedHandleNPCAI(SOLDIERTYPE * pSoldier)977 static void TurnBasedHandleNPCAI(SOLDIERTYPE* pSoldier)
978 {
979 	/*
980 	// If man is inactive/at base/dead/unconscious
981 	if (!pSoldier->bActive || !pSoldier->bInSector || (pSoldier->bLife < OKLIFE))
982 	{
983 		NPCDoesNothing(pSoldier);
984 		return;
985 	}
986 
987 	if (IsOnCivTeam(pSoldier) && pSoldier->service &&
988 		(pSoldier->bNeutral || MedicsMissionIsEscort(pSoldier)))
989 	{
990 		NPCDoesNothing(pSoldier);
991 		return;
992 	}
993 	*/
994 
995 
996 
997 	/*
998 	anim = pSoldier->anitype[pSoldier->anim];
999 
1000 	// If man is down on the ground
1001 	if (anim < BREATHING)
1002 	{
1003 		// if he lacks the breath, or APs to get up this turn (life checked above)
1004 		// OR... (new June 13/96 Ian) he's getting first aid...
1005 		if ((pSoldier->bBreath < OKBREATH) || (pSoldier->bActionPoints < (AP_GET_UP + AP_ROLL_OVER))
1006 			|| pSoldier->service)
1007 		{
1008 			NPCDoesNothing(pSoldier);
1009 			return;
1010 		}
1011 		else
1012 		{
1013 			// wait until he gets up first, only then worry about deciding his AI
1014 			return;
1015 		}
1016 	}
1017 
1018 
1019 	// if NPC's has been forced to stop by an opponent's interrupt or similar
1020 	if (pSoldier->forcedToStop)
1021 	{
1022 		return;
1023 	}
1024 
1025 	// if we are still in the midst in an uninterruptable animation
1026 	if (!AnimControl[anim].interruptable)
1027 	{
1028 		return;      // wait a while, let the animation finish first
1029 	}
1030 
1031 	*/
1032 
1033 	// yikes, this shouldn't occur! we should be trying to finish our move!
1034 	// pSoldier->fNoAPToFinishMove = FALSE;
1035 
1036 	/*
1037 	// move this clause outside of the function...
1038 	if (pSoldier->bNewSituation)
1039 		// don't force, don't want escorted mercs reacting to new opponents, etc.
1040 		CancelAIAction(pSoldier);
1041 
1042 	*/
1043 
1044 	if ((pSoldier->bAction != AI_ACTION_NONE) && pSoldier->bActionInProgress)
1045 	{
1046 		/*
1047 		if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL)
1048 		{
1049 			if (pSoldier->ubPathIndex == pSoldier->ubPathDataSize)
1050 			//if (pSoldier->usActionData == pSoldier->sGridNo )
1051 			//(IC?) if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && ( pSoldier->ubPathIndex == pSoldier->ubPathDataSize ) )
1052 			//(old?) if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && ( pSoldier->usActionData == pSoldier->sGridNo ) )
1053 			{
1054 			SLOGD("OPPONENT %d REACHES DEST - ACTION DONE",pSoldier->ubID);
1055 				ActionDone(pSoldier);
1056 			}
1057 
1058 			// *** TRICK- TAKE INTO ACCOUNT PAUSED FOR NO TIME ( FOR NOW )
1059 			if (pSoldier->fNoAPToFinishMove)
1060 			//if (pSoldier->bAction == AI_ACTION_RANDOM_PATROL && pSoldier->fNoAPToFinishMove)
1061 			{
1062 				// OK, we have a move to finish...
1063 				SLOGD("GONNA TRY TO CONTINUE PATH FOR %d", pSoldier->ubID);
1064 				SoldierTriesToContinueAlongPath(pSoldier);
1065 
1066 				// since we just gave up on our action due to running out of points, better end our turn
1067 				//EndAIGuysTurn(*pSoldier);
1068 			}
1069 		}
1070 		*/
1071 
1072 		// if action should remain in progress
1073 		if (ActionInProgress(pSoldier))
1074 		{
1075 			// let it continue
1076 			return;
1077 		}
1078 	}
1079 
1080 
1081 	SLOGD("HandleManAI - DECIDING for guynum %d(%s) at gridno %d, APs %d",
1082 		pSoldier->ubID, pSoldier->name.c_str(), pSoldier->sGridNo, pSoldier->bActionPoints);
1083 
1084 
1085 	// if man has nothing to do
1086 	if (pSoldier->bAction == AI_ACTION_NONE)
1087 	{
1088 		// make sure this flag is turned off (it already should be!)
1089 		pSoldier->bActionInProgress = FALSE;
1090 
1091 		// Since we're NEVER going to "continue" along an old path at this point,
1092 		// then it would be nice place to reinitialize "pathStored" flag for
1093 		// insurance purposes.
1094 		//
1095 		// The "pathStored" variable controls whether it's necessary to call
1096 		// findNewPath() after you've called NewDest(). Since the AI calls
1097 		// findNewPath() itself, a speed gain can be obtained by avoiding
1098 		// redundancy.
1099 		//
1100 		// The "normal" way for pathStored to be reset is inside
1101 		// SetNewCourse() [which gets called after NewDest()].
1102 		//
1103 		// The only reason we would NEED to reinitialize it here is if I've
1104 		// incorrectly set pathStored to TRUE in a process that doesn't end up
1105 		// calling NewDest()
1106 		pSoldier->bPathStored = FALSE;
1107 
1108 		// decide on the next action
1109 		if (pSoldier->bNextAction != AI_ACTION_NONE)
1110 		{
1111 			// do the next thing we have to do...
1112 			if ( pSoldier->bNextAction == AI_ACTION_END_COWER_AND_MOVE )
1113 			{
1114 				if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1115 				{
1116 					pSoldier->bAction = AI_ACTION_STOP_COWERING;
1117 					pSoldier->usActionData = ANIM_STAND;
1118 				}
1119 				else if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight < ANIM_STAND )
1120 				{
1121 					// stand up!
1122 					pSoldier->bAction = AI_ACTION_CHANGE_STANCE;
1123 					pSoldier->usActionData = ANIM_STAND;
1124 				}
1125 				else
1126 				{
1127 					pSoldier->bAction = AI_ACTION_NONE;
1128 				}
1129 				if ( pSoldier->sGridNo == pSoldier->usNextActionData )
1130 				{
1131 					// no need to walk after this
1132 					pSoldier->bNextAction = AI_ACTION_NONE;
1133 					pSoldier->usNextActionData = NOWHERE;
1134 				}
1135 				else
1136 				{
1137 					pSoldier->bNextAction = AI_ACTION_WALK;
1138 					// leave next-action-data as is since that's where we want to go
1139 				}
1140 			}
1141 			else
1142 			{
1143 				pSoldier->bAction = pSoldier->bNextAction;
1144 				pSoldier->usActionData = pSoldier->usNextActionData;
1145 				pSoldier->bTargetLevel = pSoldier->bNextTargetLevel;
1146 				pSoldier->bNextAction = AI_ACTION_NONE;
1147 				pSoldier->usNextActionData = 0;
1148 				pSoldier->bNextTargetLevel = 0;
1149 			}
1150 			if (pSoldier->bAction == AI_ACTION_PICKUP_ITEM)
1151 			{
1152 				// the item pool index was stored in the special data field
1153 				pSoldier->uiPendingActionData1 = pSoldier->iNextActionSpecialData;
1154 			}
1155 		}
1156 		else if ( pSoldier->sAbsoluteFinalDestination != NOWHERE )
1157 		{
1158 			if ( ACTING_ON_SCHEDULE( pSoldier ) )
1159 			{
1160 				pSoldier->bAction = AI_ACTION_SCHEDULE_MOVE;
1161 			}
1162 			else
1163 			{
1164 				pSoldier->bAction = AI_ACTION_WALK;
1165 			}
1166 			pSoldier->usActionData = pSoldier->sAbsoluteFinalDestination;
1167 		}
1168 		else
1169 		{
1170 			if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV))
1171 			{
1172 				if (CREATURE_OR_BLOODCAT( pSoldier ))
1173 				{
1174 					pSoldier->bAction = CreatureDecideAction( pSoldier );
1175 				}
1176 				else if (pSoldier->ubBodyType == CROW)
1177 				{
1178 					pSoldier->bAction = CrowDecideAction( pSoldier );
1179 				}
1180 				else
1181 				{
1182 					pSoldier->bAction = DecideAction(pSoldier);
1183 				}
1184 			}
1185 		}
1186 
1187 		if (pSoldier->bAction == AI_ACTION_ABSOLUTELY_NONE)
1188 		{
1189 			pSoldier->bAction = AI_ACTION_NONE;
1190 		}
1191 
1192 		// if he chose to continue doing nothing
1193 		if (pSoldier->bAction == AI_ACTION_NONE)
1194 		{
1195 			NPCDoesNothing(pSoldier);  // sets pSoldier->moved to TRUE
1196 			return;
1197 		}
1198 
1199 
1200 
1201 		/*
1202 		// if we somehow just caused an uninterruptable animation to occur
1203 		// This is mainly to finish a weapon_AWAY anim that preceeds a TOSS attack
1204 		if (!AnimControl[ pSoldier->anitype[pSoldier->anim] ].interruptable)
1205 		{
1206 			SLOGD("Uninterruptable animation %d, skipping guy %d",pSoldier->anitype[pSoldier->anim],pSoldier->ubID);
1207 
1208 			return;      // wait a while, let the animation finish first
1209 		}
1210 		*/
1211 
1212 		// to get here, we MUST have an action selected, but not in progress...
1213 
1214 		// see if we can afford to do this action
1215 		if (IsActionAffordable(pSoldier))
1216 		{
1217 			NPCDoesAct(pSoldier);
1218 
1219 			// perform the chosen action
1220 			pSoldier->bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
1221 
1222 			if ( !pSoldier->bActionInProgress && pSoldier->sAbsoluteFinalDestination != NOWHERE )
1223 			{
1224 				// turn based... abort this guy's turn
1225 				EndAIGuysTurn(*pSoldier);
1226 			}
1227 		}
1228 		else
1229 		{
1230 			HaltMoveForSoldierOutOfPoints(*pSoldier);
1231 			return;
1232 		}
1233 	}
1234 }
1235 
1236 
1237 static void ManChecksOnFriends(SOLDIERTYPE* pSoldie);
1238 
1239 
RefreshAI(SOLDIERTYPE * pSoldier)1240 void RefreshAI(SOLDIERTYPE *pSoldier)
1241 {
1242 	// produce our own private "mine map" so we can avoid the ones we can detect
1243 	// MarkDetectableMines(pSoldier);
1244 
1245 	// whether last attack hit or not doesn't matter once control has been lost
1246 	pSoldier->bLastAttackHit = FALSE;
1247 
1248 	// get an up-to-date alert status for this guy
1249 	DecideAlertStatus(pSoldier);
1250 
1251 	if (pSoldier->bAlertStatus == STATUS_YELLOW)
1252 		SkipCoverCheck = FALSE;
1253 
1254 	// if he's in battle or knows opponents are here
1255 	if (gfTurnBasedAI)
1256 	{
1257 		if ((pSoldier->bAlertStatus == STATUS_BLACK) || (pSoldier->bAlertStatus == STATUS_RED))
1258 		{
1259 			// always freshly rethink things at start of his turn
1260 			pSoldier->bNewSituation = IS_NEW_SITUATION;
1261 		}
1262 		else
1263 		{
1264 			// make sure any paths stored during out last AI decision but not reacted
1265 			// to (probably due to lack of APs) get re-tested by the ExecuteAction()
1266 			// function in AI, since the ->sDestination may no longer be legal now!
1267 			pSoldier->bPathStored = FALSE;
1268 
1269 			// if not currently engaged, or even alerted
1270 			// take a quick look around to see if any friends seem to be in trouble
1271 			ManChecksOnFriends(pSoldier);
1272 
1273 			// allow stationary GREEN Civilians to turn again at least 1/turn!
1274 		}
1275 		pSoldier->bLastAction = AI_ACTION_NONE;
1276 
1277 	}
1278 }
1279 
1280 
AIDecideRadioAnimation(SOLDIERTYPE * pSoldier)1281 static void AIDecideRadioAnimation(SOLDIERTYPE* pSoldier)
1282 {
1283 	if ( pSoldier->ubBodyType != REGMALE && pSoldier->ubBodyType != BIGMALE )
1284 	{
1285 		// no animation available
1286 		ActionDone( pSoldier );
1287 		return;
1288 	}
1289 
1290 	if (IsOnCivTeam(pSoldier) && pSoldier->ubCivilianGroup != KINGPIN_CIV_GROUP)
1291 	{
1292 		// don't play anim
1293 		ActionDone( pSoldier );
1294 		return;
1295 	}
1296 
1297 	switch( gAnimControl[ pSoldier->usAnimState ].ubEndHeight )
1298 	{
1299 		case ANIM_STAND:
1300 
1301 			EVENT_InitNewSoldierAnim( pSoldier, AI_RADIO, 0 , FALSE );
1302 			break;
1303 
1304 		case ANIM_CROUCH:
1305 
1306 			EVENT_InitNewSoldierAnim( pSoldier, AI_CR_RADIO, 0 , FALSE );
1307 			break;
1308 
1309 		case ANIM_PRONE:
1310 
1311 			ActionDone( pSoldier );
1312 			break;
1313 	}
1314 }
1315 
1316 
ExecuteAction(SOLDIERTYPE * pSoldier)1317 INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
1318 {
1319 	// in most cases, merc will change location, or may cause damage to opponents,
1320 	// so a new cover check will be necessary.  Exceptions handled individually.
1321 	SkipCoverCheck = FALSE;
1322 
1323 	// reset this field, too
1324 	pSoldier->bLastAttackHit = FALSE;
1325 
1326 	if (gfTurnBasedAI || gTacticalStatus.fAutoBandageMode)
1327 	{
1328 		SLOGD(ST::format("{} does {} (a.d. {}) in {} with {} APs left",
1329 			pSoldier->ubID, gzActionStr[pSoldier->bAction], pSoldier->usActionData,
1330 			pSoldier->sGridNo, pSoldier->bActionPoints));
1331 	}
1332 
1333 	SLOGD(ST::format("{} does {} (a.d. {}) at time {}", pSoldier->ubID,
1334 		gzActionStr[pSoldier->bAction], pSoldier->usActionData, GetJA2Clock()));
1335 
1336 	switch (pSoldier->bAction)
1337 	{
1338 		case AI_ACTION_NONE:                  // maintain current position & facing
1339 			// do nothing
1340 			break;
1341 
1342 		case AI_ACTION_WAIT: // hold AI_ACTION_NONE for a specified time
1343 			if (gfTurnBasedAI)
1344 			{
1345 				// probably an action set as a next-action in the realtime prior to combat
1346 				// do nothing
1347 			}
1348 			else
1349 			{
1350 				RESETTIMECOUNTER( pSoldier->AICounter, pSoldier->usActionData );
1351 				if (pSoldier->ubProfile != NO_PROFILE)
1352 				{
1353 					SLOGD("%s waiting %d from %d",
1354 								pSoldier->name.c_str(), pSoldier->AICounter, GetJA2Clock());
1355 				}
1356 			}
1357 			ActionDone( pSoldier );
1358 			break;
1359 
1360 		case AI_ACTION_CHANGE_FACING:         // turn this way & that to look
1361 			// as long as we don't see anyone new, cover won't have changed
1362 			// if we see someone new, it will cause a new situation & remove this
1363 			SkipCoverCheck = TRUE;
1364 
1365 			SLOGD("ExecuteAction: SkipCoverCheck ON");
1366 
1367 			//pSoldier->bDesiredDirection = (UINT8) ;   // turn to face direction in actionData
1368 			SendSoldierSetDesiredDirectionEvent( pSoldier, pSoldier->usActionData );
1369 			// now we'll have to wait for the turning to finish; no need to call TurnSoldier here
1370 			//TurnSoldier( pSoldier );
1371 			/*
1372 			if (!StartTurn(pSoldier,pSoldier->usActionData,FASTTURN))
1373 			{
1374 				// ZAP NPC's remaining action points so this isn't likely to repeat
1375 				pSoldier->bActionPoints = 0;
1376 
1377 				CancelAIAction(pSoldier);
1378 				return(FALSE);         // nothing is in progress
1379 			}
1380 			*/
1381 			break;
1382 
1383 		case AI_ACTION_PICKUP_ITEM: // grab something!
1384 			SoldierPickupItem( pSoldier, pSoldier->uiPendingActionData1, pSoldier->usActionData, 0 );
1385 			break;
1386 
1387 		case AI_ACTION_DROP_ITEM: // drop item in hand
1388 			SoldierDropItem( pSoldier, &(pSoldier->inv[HANDPOS]) );
1389 			DeleteObj( &(pSoldier->inv[HANDPOS]) );
1390 			pSoldier->bAction = AI_ACTION_PENDING_ACTION;
1391 			break;
1392 
1393 		case AI_ACTION_MOVE_TO_CLIMB:
1394 			if ( pSoldier->usActionData == pSoldier->sGridNo )
1395 			{
1396 				// change action to climb now and try that.
1397 				pSoldier->bAction = AI_ACTION_CLIMB_ROOF;
1398 				if (IsActionAffordable(pSoldier))
1399 				{
1400 					return( ExecuteAction( pSoldier ) );
1401 				}
1402 				else
1403 				{
1404 					// no action started
1405 					return( FALSE );
1406 				}
1407 			}
1408 			// fall through
1409 		case AI_ACTION_RANDOM_PATROL:         // move towards a particular location
1410 		case AI_ACTION_SEEK_FRIEND:           // move towards friend in trouble
1411 		case AI_ACTION_SEEK_OPPONENT:         // move towards a reported opponent
1412 		case AI_ACTION_TAKE_COVER:            // run for nearest cover from threat
1413 		case AI_ACTION_GET_CLOSER:            // move closer to a strategic location
1414 
1415 		case AI_ACTION_POINT_PATROL:          // move towards next patrol point
1416 		case AI_ACTION_LEAVE_WATER_GAS:       // seek nearest spot of ungassed land
1417 		case AI_ACTION_SEEK_NOISE:            // seek most important noise heard
1418 		case AI_ACTION_RUN_AWAY:              // run away from nearby opponent(s)
1419 
1420 		case AI_ACTION_APPROACH_MERC:         // walk up to someone to talk
1421 		case AI_ACTION_TRACK:                 // track by ground scent
1422 		case AI_ACTION_EAT:                   // monster approaching corpse
1423 		case AI_ACTION_SCHEDULE_MOVE:
1424 		case AI_ACTION_WALK:
1425 		case AI_ACTION_RUN:
1426 
1427 			if ( gfTurnBasedAI && pSoldier->bAlertStatus < STATUS_BLACK )
1428 			{
1429 				if ( pSoldier->sLastTwoLocations[0] == NOWHERE )
1430 				{
1431 					pSoldier->sLastTwoLocations[0] = pSoldier->sGridNo;
1432 				}
1433 				else if ( pSoldier->sLastTwoLocations[1] == NOWHERE )
1434 				{
1435 					pSoldier->sLastTwoLocations[1] = pSoldier->sGridNo;
1436 				}
1437 				// check for loop
1438 				else if ( pSoldier->usActionData == pSoldier->sLastTwoLocations[1] && pSoldier->sGridNo == pSoldier->sLastTwoLocations[0] )
1439 				{
1440 					SLOGD("%d in movement loop, aborting turn", pSoldier->ubID);
1441 
1442 					// loop found!
1443 					ActionDone( pSoldier );
1444 					EndAIGuysTurn(*pSoldier);
1445 				}
1446 				else
1447 				{
1448 					pSoldier->sLastTwoLocations[0] = pSoldier->sLastTwoLocations[1];
1449 					pSoldier->sLastTwoLocations[1] = pSoldier->sGridNo;
1450 				}
1451 			}
1452 
1453 			// Randomly do growl...
1454 			if ( pSoldier->ubBodyType == BLOODCAT )
1455 			{
1456 				if ( ( gTacticalStatus.uiFlags & INCOMBAT ) )
1457 				{
1458 					if ( Random( 2 ) == 0 )
1459 					{
1460 						PlaySoldierJA2Sample(pSoldier, SoundRange<BLOODCAT_GROWL_1, BLOODCAT_GROWL_4>(), HIGHVOLUME, 1, TRUE);
1461 					}
1462 				}
1463 			}
1464 
1465 			// on YELLOW/GREEN status, NPCs keep the actions from turn to turn
1466 			// (newSituation is intentionally NOT set in NewSelectedNPC()), so the
1467 			// possibility exists that NOW the actionData is no longer a valid
1468 			// NPC ->sDestination (path got blocked, someone is now standing at that
1469 			// gridno, etc.)  So we gotta check again that the ->sDestination's legal!
1470 
1471 			// optimization - Ian (if up-to-date path is known, do not check again)
1472 			if (!pSoldier->bPathStored)
1473 			{
1474 				if ( (pSoldier->sAbsoluteFinalDestination != NOWHERE || gTacticalStatus.fAutoBandageMode) && !(gTacticalStatus.uiFlags & INCOMBAT) )
1475 				{
1476 					// NPC system move, allow path through
1477 					if (LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE ))
1478 					{
1479 						// optimization - Ian: prevent another path call in SetNewCourse()
1480 						pSoldier->bPathStored = TRUE;
1481 					}
1482 				}
1483 				else
1484 				{
1485 					if (LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, 0))
1486 					{
1487 						// optimization - Ian: prevent another path call in SetNewCourse()
1488 						pSoldier->bPathStored = TRUE;
1489 					}
1490 				}
1491 
1492 				// if we STILL don't have a path
1493 				if ( !pSoldier->bPathStored )
1494 				{
1495 					// Check if we were told to move by NPC stuff
1496 					if ( pSoldier->sAbsoluteFinalDestination != NOWHERE && !(gTacticalStatus.uiFlags & INCOMBAT) )
1497 					{
1498 						SLOGE("AI %s failed to get path for dialogue-related move!", pSoldier->name.c_str());
1499 
1500 						// Are we close enough?
1501 						if ( !ACTING_ON_SCHEDULE( pSoldier ) && SpacesAway( pSoldier->sGridNo, pSoldier->sAbsoluteFinalDestination ) < 4 )
1502 						{
1503 							// This is close enough...
1504 							ReplaceLocationInNPCDataFromProfileID( pSoldier->ubProfile, pSoldier->sAbsoluteFinalDestination, pSoldier->sGridNo );
1505 							NPCGotoGridNo( pSoldier->ubProfile, pSoldier->sGridNo, (UINT8) (pSoldier->ubQuoteRecord - 1) );
1506 						}
1507 						else
1508 						{
1509 							// This is important, so try taking a path through people (and bumping them aside)
1510 							if (LegalNPCDestination(pSoldier,pSoldier->usActionData,ENSURE_PATH,WATEROK, PATH_THROUGH_PEOPLE))
1511 							{
1512 								// optimization - Ian: prevent another path call in SetNewCourse()
1513 								pSoldier->bPathStored = TRUE;
1514 							}
1515 							else
1516 							{
1517 								// Have buddy wait a while...
1518 								pSoldier->bNextAction = AI_ACTION_WAIT;
1519 								pSoldier->usNextActionData = (UINT16)REALTIME_AI_DELAY;
1520 							}
1521 						}
1522 
1523 						if (!pSoldier->bPathStored)
1524 						{
1525 							CancelAIAction(pSoldier);
1526 							return(FALSE);         // nothing is in progress
1527 						}
1528 					}
1529 					else
1530 					{
1531 						CancelAIAction(pSoldier);
1532 						return(FALSE);         // nothing is in progress
1533 					}
1534 				}
1535 			}
1536 
1537 			// add on anything necessary to traverse off map edge
1538 			switch( pSoldier->ubQuoteActionID )
1539 			{
1540 				case QUOTE_ACTION_ID_TRAVERSE_EAST:
1541 					pSoldier->sOffWorldGridNo = pSoldier->usActionData;
1542 					AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, EAST );
1543 					break;
1544 				case QUOTE_ACTION_ID_TRAVERSE_SOUTH:
1545 					pSoldier->sOffWorldGridNo = pSoldier->usActionData;
1546 					AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, SOUTH );
1547 					break;
1548 				case QUOTE_ACTION_ID_TRAVERSE_WEST:
1549 					pSoldier->sOffWorldGridNo = pSoldier->usActionData;
1550 					AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, WEST );
1551 					break;
1552 				case QUOTE_ACTION_ID_TRAVERSE_NORTH:
1553 					pSoldier->sOffWorldGridNo = pSoldier->usActionData;
1554 					AdjustSoldierPathToGoOffEdge( pSoldier, pSoldier->usActionData, NORTH );
1555 					break;
1556 				default:
1557 					break;
1558 			}
1559 
1560 			NewDest(pSoldier,pSoldier->usActionData);    // set new ->sDestination to actionData
1561 
1562 			// make sure it worked (check that pSoldier->sDestination == pSoldier->usActionData)
1563 			if (pSoldier->sFinalDestination != pSoldier->usActionData)
1564 			{
1565 				// temporarily black list this gridno to stop enemy from going there
1566 				pSoldier->sBlackList = (INT16) pSoldier->usActionData;
1567 
1568 				SLOGW("Setting blacklist for %d to %d",
1569 							pSoldier->ubID, pSoldier->sBlackList);
1570 
1571 				CancelAIAction(pSoldier);
1572 				return(FALSE);         // nothing is in progress
1573 			}
1574 
1575 			// cancel any old black-listed gridno, got a valid new ->sDestination
1576 			pSoldier->sBlackList = NOWHERE;
1577 			break;
1578 
1579 		case AI_ACTION_ESCORTED_MOVE:         // go where told to by escortPlayer
1580 			// since this is a delayed move, gotta make sure that it hasn't become
1581 			// illegal since escort orders were issued (->sDestination/route blocked).
1582 			// So treat it like a CONTINUE movement, and handle errors that way
1583 			if (!TryToResumeMovement(pSoldier,pSoldier->usActionData))
1584 			{
1585 				// don't black-list anything here, and action already got canceled
1586 				return(FALSE);         // nothing is in progress
1587 			}
1588 
1589 			// cancel any old black-listed gridno, got a valid new ->sDestination
1590 			pSoldier->sBlackList = NOWHERE;
1591 			break;
1592 
1593 		case AI_ACTION_TOSS_PROJECTILE:       // throw grenade at/near opponent(s)
1594 			LoadWeaponIfNeeded(pSoldier);
1595 			// fallthrough
1596 
1597 		case AI_ACTION_KNIFE_MOVE:            // preparing to stab opponent
1598 			if (pSoldier->bAction == AI_ACTION_KNIFE_MOVE) // if statement because toss falls through
1599 			{
1600 				pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_KNIFE_MOVE );
1601 			}
1602 			// fallthrough
1603 
1604 		case AI_ACTION_FIRE_GUN:              // shoot at nearby opponent
1605 		case AI_ACTION_THROW_KNIFE:						// throw knife at nearby opponent
1606 		{
1607 			// randomly decide whether to say civ quote
1608 			if ( pSoldier->bVisible != -1 && pSoldier->bTeam != MILITIA_TEAM )
1609 			{
1610 				// ATE: Make sure it's a person :)
1611 				if ( IS_MERC_BODY_TYPE( pSoldier ) && pSoldier->ubProfile == NO_PROFILE )
1612 				{
1613 					// CC, ATE here - I put in some TEMP randomness...
1614 					if ( Random( 50 ) == 0 )
1615 					{
1616 						StartCivQuote( pSoldier );
1617 					}
1618 				}
1619 			}
1620 
1621 			ItemHandleResult const iRetCode = HandleItem(pSoldier, pSoldier->usActionData, pSoldier->bTargetLevel, pSoldier->inv[HANDPOS].usItem, FALSE);
1622 			if ( iRetCode != ITEM_HANDLE_OK)
1623 			{
1624 				if ( iRetCode != ITEM_HANDLE_BROKEN ) // if the item broke, this is 'legal' and doesn't need reporting
1625 				{
1626 					SLOGW("AI %d got error code %d from HandleItem, doing action %d, has %d APs... aborting deadlock!",
1627 								pSoldier->ubID, iRetCode, pSoldier->bAction, pSoldier->bActionPoints);
1628 				}
1629 				CancelAIAction(pSoldier);
1630 				if (gfTurnBasedAI)
1631 				{
1632 					SLOGD("Ending turn for %d because of error from HandleItem", pSoldier->ubID);
1633 				}
1634 				EndAIGuysTurn(*pSoldier);
1635 			}
1636 			break;
1637 		}
1638 
1639 		case AI_ACTION_PULL_TRIGGER:          // activate an adjacent panic trigger
1640 
1641 			// turn to face trigger first
1642 			if ( FindStructure( (INT16)(pSoldier->sGridNo + DirectionInc( NORTH )), STRUCTURE_SWITCH ) )
1643 			{
1644 				SendSoldierSetDesiredDirectionEvent( pSoldier, NORTH );
1645 			}
1646 			else
1647 			{
1648 				SendSoldierSetDesiredDirectionEvent( pSoldier, WEST );
1649 			}
1650 
1651 			EVENT_InitNewSoldierAnim( pSoldier, AI_PULL_SWITCH, 0 , FALSE );
1652 
1653 			DeductPoints( pSoldier, AP_PULL_TRIGGER, 0 );
1654 
1655 			//gTacticalStatus.fPanicFlags					= 0; // turn all flags off
1656 			gTacticalStatus.the_chosen_one = NULL;
1657 			break;
1658 
1659 		case AI_ACTION_USE_DETONATOR:
1660 			//gTacticalStatus.fPanicFlags					= 0; // turn all flags off
1661 			gTacticalStatus.the_chosen_one = NULL;
1662 			//gTacticalStatus.sPanicTriggerGridno	= NOWHERE;
1663 
1664 			// grab detonator and set off bomb(s)
1665 			DeductPoints( pSoldier, AP_USE_REMOTE, BP_USE_DETONATOR);// pay for it!
1666 			SetOffPanicBombs(pSoldier, 0);
1667 
1668 			// action completed immediately, cancel it right away
1669 			pSoldier->usActionData = NOWHERE;
1670 			pSoldier->bLastAction = pSoldier->bAction;
1671 			pSoldier->bAction = AI_ACTION_NONE;
1672 			return(FALSE);           // no longer in progress
1673 
1674 		case AI_ACTION_RED_ALERT:             // tell friends opponent(s) seen
1675 			// if a computer merc, and up to now they didn't know you're here
1676 			if (!(pSoldier->uiStatusFlags & SOLDIER_PC) &&
1677 				(
1678 					!gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition ||
1679 					(
1680 						gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE &&
1681 						gTacticalStatus.the_chosen_one == NULL
1682 					)
1683 				))
1684 			{
1685 				HandleInitialRedAlert(pSoldier->bTeam);
1686 			}
1687 			SLOGD("AI radios your position!" );
1688 			// fallthrough
1689 		case AI_ACTION_YELLOW_ALERT:          // tell friends opponent(s) heard
1690 			SLOGD("Debug: AI radios about a noise!" );
1691 			DeductPoints(pSoldier,AP_RADIO,BP_RADIO);// pay for it!
1692 			RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam);      // about everybody
1693 			// action completed immediately, cancel it right away
1694 
1695 			// ATE: Change to an animation!
1696 			AIDecideRadioAnimation( pSoldier );
1697 			//return(FALSE);           // no longer in progress
1698 			break;
1699 
1700 		case AI_ACTION_CREATURE_CALL:									// creature calling to others
1701 			DeductPoints(pSoldier,AP_RADIO,BP_RADIO);// pay for it!
1702 			CreatureCall( pSoldier );
1703 			//return( FALSE ); // no longer in progress
1704 			break;
1705 
1706 		case AI_ACTION_CHANGE_STANCE:                // crouch
1707 			if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == pSoldier->usActionData )
1708 			{
1709 				// abort!
1710 				ActionDone( pSoldier );
1711 				return( FALSE );
1712 			}
1713 
1714 			SkipCoverCheck = TRUE;
1715 			SLOGD("ExecuteAction: SkipCoverCheck ON");
1716 			ChangeSoldierStance(pSoldier, pSoldier->usActionData);
1717 			break;
1718 
1719 		case AI_ACTION_COWER:
1720 			// make sure action data is set right
1721 			if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1722 			{
1723 				// nothing to do!
1724 				ActionDone( pSoldier );
1725 				return( FALSE );
1726 			}
1727 			else
1728 			{
1729 				pSoldier->usActionData = ANIM_CROUCH;
1730 				SetSoldierCowerState( pSoldier, TRUE );
1731 			}
1732 			break;
1733 
1734 		case AI_ACTION_STOP_COWERING:
1735 			// make sure action data is set right
1736 			if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1737 			{
1738 				pSoldier->usActionData = ANIM_STAND;
1739 				SetSoldierCowerState( pSoldier, FALSE );
1740 			}
1741 			else
1742 			{
1743 				// nothing to do!
1744 				ActionDone( pSoldier );
1745 				return( FALSE );
1746 			}
1747 			break;
1748 
1749 		case AI_ACTION_GIVE_AID:              // help injured/dying friend
1750 		{
1751 			//pSoldier->usUIMovementMode = RUNNING;
1752 			ItemHandleResult const iRetCode = HandleItem(pSoldier, pSoldier->usActionData, 0, pSoldier->inv[HANDPOS].usItem, FALSE);
1753 			if ( iRetCode != ITEM_HANDLE_OK)
1754 			{
1755 				SLOGW("AI %d got error code %d from HandleItem, doing action %d... aborting deadlock!",
1756 							pSoldier->ubID, iRetCode, pSoldier->bAction);
1757 				CancelAIAction(pSoldier);
1758 				EndAIGuysTurn(*pSoldier);
1759 			}
1760 			break;
1761 		}
1762 
1763 		case AI_ACTION_OPEN_OR_CLOSE_DOOR:
1764 		case AI_ACTION_UNLOCK_DOOR:
1765 		case AI_ACTION_LOCK_DOOR:
1766 			{
1767 				STRUCTURE *pStructure;
1768 				INT8      bDirection;
1769 				INT16     sDoorGridNo;
1770 
1771 				bDirection = (INT8) GetDirectionFromGridNo( pSoldier->usActionData, pSoldier );
1772 				if (bDirection == EAST || bDirection == SOUTH)
1773 				{
1774 					sDoorGridNo = pSoldier->sGridNo;
1775 				}
1776 				else
1777 				{
1778 					sDoorGridNo = pSoldier->sGridNo + DirectionInc( bDirection );
1779 				}
1780 
1781 				pStructure = FindStructure( sDoorGridNo, STRUCTURE_ANYDOOR );
1782 				if (pStructure == NULL)
1783 				{
1784 					SLOGD("AI %d tried to open door it could not then find in %d",
1785 								pSoldier->ubID, sDoorGridNo );
1786 					CancelAIAction(pSoldier);
1787 					EndAIGuysTurn(*pSoldier);
1788 				}
1789 
1790 				StartInteractiveObject(sDoorGridNo, *pStructure, *pSoldier, bDirection);
1791 				InteractWithOpenableStruct(*pSoldier, *pStructure, bDirection);
1792 			}
1793 			break;
1794 
1795 		case AI_ACTION_LOWER_GUN:
1796 			// for now, just do "action done"
1797 			ActionDone( pSoldier );
1798 			break;
1799 
1800 		case AI_ACTION_CLIMB_ROOF:
1801 			if (pSoldier->bLevel == 0)
1802 			{
1803 				BeginSoldierClimbUpRoof( pSoldier );
1804 			}
1805 			else
1806 			{
1807 				BeginSoldierClimbDownRoof( pSoldier );
1808 			}
1809 			break;
1810 
1811 		case AI_ACTION_END_TURN:
1812 			ActionDone( pSoldier );
1813 			if (gfTurnBasedAI)
1814 			{
1815 				EndAIGuysTurn(*pSoldier);
1816 			}
1817 			return( FALSE );         // nothing is in progress
1818 
1819 		case AI_ACTION_TRAVERSE_DOWN:
1820 			if (gfTurnBasedAI)
1821 			{
1822 				EndAIGuysTurn(*pSoldier);
1823 			}
1824 			if ( pSoldier->ubProfile != NO_PROFILE )
1825 			{
1826 				gMercProfiles[ pSoldier->ubProfile ].bSectorZ++;
1827 				gMercProfiles[ pSoldier->ubProfile ].fUseProfileInsertionInfo = FALSE;
1828 			}
1829 			TacticalRemoveSoldier(*pSoldier);
1830 			CheckForEndOfBattle( TRUE );
1831 
1832 			return( FALSE );         // nothing is in progress
1833 
1834 		case AI_ACTION_OFFER_SURRENDER:
1835 			// start the offer of surrender!
1836 			StartCivQuote( pSoldier );
1837 			break;
1838 
1839 		default:
1840 			return(FALSE);
1841 	}
1842 
1843 	// return status indicating execution of action was properly started
1844 	return(TRUE);
1845 }
1846 
CheckForChangingOrders(SOLDIERTYPE * pSoldier)1847 void CheckForChangingOrders(SOLDIERTYPE *pSoldier)
1848 {
1849 	switch( pSoldier->bAlertStatus )
1850 	{
1851 		case STATUS_GREEN:
1852 			if ( !CREATURE_OR_BLOODCAT( pSoldier ) )
1853 			{
1854 				if ( pSoldier->bTeam == CIV_TEAM && pSoldier->ubProfile != NO_PROFILE && pSoldier->bNeutral && gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo != NOWHERE && pSoldier->ubCivilianGroup != QUEENS_CIV_GROUP )
1855 				{
1856 					// must make them uncower first, then return to start location
1857 					pSoldier->bNextAction = AI_ACTION_END_COWER_AND_MOVE;
1858 					pSoldier->usNextActionData = gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo;
1859 					gMercProfiles[ pSoldier->ubProfile ].sPreCombatGridNo = NOWHERE;
1860 				}
1861 				else if ( pSoldier->uiStatusFlags & SOLDIER_COWERING )
1862 				{
1863 					pSoldier->bNextAction = AI_ACTION_STOP_COWERING;
1864 					pSoldier->usNextActionData = ANIM_STAND;
1865 				}
1866 				else
1867 				{
1868 					pSoldier->bNextAction = AI_ACTION_CHANGE_STANCE;
1869 					pSoldier->usNextActionData = ANIM_STAND;
1870 				}
1871 			}
1872 			break;
1873 		case STATUS_YELLOW:
1874 			break;
1875 		default:
1876 			if ((pSoldier->bOrders == ONGUARD) || (pSoldier->bOrders == CLOSEPATROL))
1877 			{
1878 				// crank up ONGUARD to CLOSEPATROL, and CLOSEPATROL to FARPATROL
1879 				pSoldier->bOrders++;       // increase roaming range by 1 category
1880 			}
1881 			else if ( pSoldier->bTeam == MILITIA_TEAM )
1882 			{
1883 				// go on alert!
1884 				pSoldier->bOrders = SEEKENEMY;
1885 			}
1886 			else if ( CREATURE_OR_BLOODCAT( pSoldier ) )
1887 			{
1888 				if (pSoldier->bOrders != STATIONARY && pSoldier->bOrders != ONCALL)
1889 				{
1890 					pSoldier->bOrders = SEEKENEMY;
1891 				}
1892 			}
1893 
1894 			if ( pSoldier->ubProfile == WARDEN )
1895 			{
1896 				// Tixa
1897 				MakeClosestEnemyChosenOne();
1898 			}
1899 			break;
1900 	}
1901 }
1902 
InitAttackType(ATTACKTYPE * pAttack)1903 void InitAttackType(ATTACKTYPE *pAttack)
1904 {
1905 	// initialize the given bestAttack structure fields to their default values
1906 	pAttack->ubPossible          = FALSE;
1907 	pAttack->opponent            = NULL;
1908 	pAttack->ubAimTime           = 0;
1909 	pAttack->ubChanceToReallyHit = 0;
1910 	pAttack->sTarget             = NOWHERE;
1911 	pAttack->iAttackValue        = 0;
1912 	pAttack->ubAPCost            = 0;
1913 }
1914 
HandleInitialRedAlert(INT8 bTeam)1915 void HandleInitialRedAlert(INT8 bTeam)
1916 {
1917 	if (!gTacticalStatus.Team[bTeam].bAwareOfOpposition)
1918 	{
1919 		SLOGD("Enemies on team %d prompted to go on RED ALERT!", bTeam );
1920 	}
1921 
1922 	// if there is a stealth mission in progress here, and a panic trigger exists
1923 	if ( bTeam == ENEMY_TEAM && (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
1924 	{
1925 		// they're going to be aware of us now!
1926 		MakeClosestEnemyChosenOne();
1927 	}
1928 
1929 	if ( bTeam == ENEMY_TEAM && gWorldSectorX == 3 && gWorldSectorY == MAP_ROW_P && gbWorldSectorZ == 0 )
1930 	{
1931 		// alert Queen and Joe if they are around
1932 		SOLDIERTYPE *			pSoldier;
1933 
1934 		pSoldier = FindSoldierByProfileID(QUEEN);
1935 		if ( pSoldier )
1936 		{
1937 			pSoldier->bAlertStatus = STATUS_RED;
1938 		}
1939 
1940 		pSoldier = FindSoldierByProfileID(JOE);
1941 		if ( pSoldier )
1942 		{
1943 			pSoldier->bAlertStatus = STATUS_RED;
1944 		}
1945 	}
1946 
1947 	// remember enemies are alerted, prevent another red alert from happening
1948 	gTacticalStatus.Team[ bTeam ].bAwareOfOpposition = TRUE;
1949 }
1950 
1951 
ManChecksOnFriends(SOLDIERTYPE * pSoldier)1952 static void ManChecksOnFriends(SOLDIERTYPE* pSoldier)
1953 {
1954 	INT16 sDistVisible;
1955 
1956 	// THIS ROUTINE SHOULD ONLY BE CALLED FOR SOLDIERS ON STATUS GREEN or YELLOW
1957 
1958 	// go through each soldier, looking for "friends" (soldiers on same side)
1959 	FOR_EACH_MERC(i)
1960 	{
1961 		const SOLDIERTYPE* const pFriend = *i;
1962 
1963 		// if this man is neutral / NOT on my side, he's not my friend
1964 		if (pFriend->bNeutral || (pSoldier->bSide != pFriend->bSide))
1965 			continue;  // next merc
1966 
1967 		// if this merc is actually ME
1968 		if (pFriend == pSoldier) continue; // next merc
1969 
1970 		sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, pFriend->sGridNo, pFriend->bLevel );
1971 		// if we can see far enough to see this friend
1972 		if (PythSpacesAway(pSoldier->sGridNo,pFriend->sGridNo) <= sDistVisible)
1973 		{
1974 			// and can trace a line of sight to his x,y coordinates
1975 			//if (1) //*** SoldierToSoldierLineOfSightTest(pSoldier,pFriend,STRAIGHT,TRUE))
1976 			if (SoldierToSoldierLineOfSightTest(pSoldier, pFriend, (UINT8)sDistVisible, TRUE))
1977 			{
1978 				// if my friend is in battle or something is clearly happening there
1979 				if ((pFriend->bAlertStatus >= STATUS_RED) || pFriend->bUnderFire || (pFriend->bLife < OKLIFE))
1980 				{
1981 					pSoldier->bAlertStatus = STATUS_RED;
1982 					CheckForChangingOrders(pSoldier);
1983 					SetNewSituation( pSoldier );
1984 					break;         // don't bother checking on any other friends
1985 				}
1986 				else
1987 				{
1988 					// if he seems suspicious or acts like he thought he heard something
1989 					// and I'm still on status GREEN
1990 					if ((pFriend->bAlertStatus == STATUS_YELLOW) &&
1991 						(pSoldier->bAlertStatus < STATUS_YELLOW))
1992 					{
1993 						pSoldier->bAlertStatus = STATUS_YELLOW;    // also get suspicious
1994 						SetNewSituation( pSoldier );
1995 						pSoldier->sNoiseGridno = pFriend->sGridNo;  // pretend FRIEND made noise
1996 						pSoldier->ubNoiseVolume = 3;                // remember this for 3 turns
1997 						// keep check other friends, too, in case any are already on RED
1998 					}
1999 				}
2000 			}
2001 		}
2002 	}
2003 }
2004 
2005 
SetNewSituation(SOLDIERTYPE * pSoldier)2006 void SetNewSituation( SOLDIERTYPE * pSoldier )
2007 {
2008 	if ( pSoldier->bTeam != OUR_TEAM )
2009 	{
2010 		if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode && !(pSoldier->bNeutral && gTacticalStatus.uiFlags & ENGAGED_IN_CONV) )
2011 		{
2012 			// allow new situation to be set
2013 			pSoldier->bNewSituation = IS_NEW_SITUATION;
2014 
2015 			if ( gTacticalStatus.ubAttackBusyCount != 0 )
2016 			{
2017 				SLOGD("bNewSituation is set for %d when ABC !=0.", pSoldier->ubID);
2018 			}
2019 
2020 			if (!(gTacticalStatus.uiFlags & INCOMBAT))
2021 			{
2022 				// reset delay if necessary!
2023 				RESETTIMECOUNTER( pSoldier->AICounter, Random( 1000 ) );
2024 			}
2025 		}
2026 	}
2027 }
2028 
2029 
HandleAITacticalTraversal(SOLDIERTYPE & s)2030 static void HandleAITacticalTraversal(SOLDIERTYPE& s)
2031 {
2032 	HandleNPCChangesForTacticalTraversal(&s);
2033 
2034 	if (s.ubProfile != NO_PROFILE &&
2035 		NPCHasUnusedRecordWithGivenApproach(s.ubProfile, APPROACH_DONE_TRAVERSAL))
2036 	{
2037 		GetProfile(s.ubProfile).ubMiscFlags3 |= PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL;
2038 	}
2039 	else
2040 	{
2041 		s.ubQuoteActionID = 0;
2042 	}
2043 	if (gfTurnBasedAI)
2044 	{
2045 		SLOGD("Ending turn for %d because traversing out", s.ubID);
2046 	}
2047 
2048 	EndAIGuysTurn(s);
2049 	RemoveManAsTarget(&s);
2050 	if (s.bTeam == CIV_TEAM && s.fAIFlags & AI_CHECK_SCHEDULE)
2051 	{
2052 		MoveSoldierFromMercToAwaySlot(&s);
2053 		s.bInSector = FALSE;
2054 	}
2055 	else
2056 	{
2057 		ProcessQueenCmdImplicationsOfDeath(&s);
2058 		TacticalRemoveSoldier(s);
2059 	}
2060 	CheckForEndOfBattle(TRUE);
2061 }
2062