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