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