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