1 #include "AIInternals.h"
2 #include "AI.h"
3 #include "Animation_Control.h"
4 #include "Isometric_Utils.h"
5 #include "PathAI.h"
6 #include "Items.h"
7 #include "World_Items.h"
8 #include "StrategicMap.h"
9 #include "Map_Screen_Interface_Map.h"
10 #include "Soldier_Profile.h"
11 #include "Quests.h"
12 #include "Queen_Command.h"
13
14 /*
15
16 Who can use the panic button?
17 * in tixa only the warden
18 * in other places only the army
19
20 */
21
PercentEnemiesKilled()22 static UINT32 PercentEnemiesKilled()
23 {
24 Assert(gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector >= 0);
25 UINT32 totalEnemies = static_cast<UINT32>(gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector) + gTacticalStatus.ubArmyGuysKilled;
26 if (totalEnemies == 0)
27 {
28 SLOGW("PercentEnemiesKilled was expecting the army in the current sector");
29 return 0;
30 }
31 return 100 * static_cast<UINT32>(gTacticalStatus.ubArmyGuysKilled) / totalEnemies;
32 }
33
MakeClosestEnemyChosenOne()34 void MakeClosestEnemyChosenOne()
35 {
36 INT16 sPathCost, sShortestPath = 1000;
37 INT8 bPanicTrigger;
38 INT16 sPanicTriggerGridNo;
39
40 if ( ! (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
41 {
42 return;
43 }
44
45 if (!NeedToRadioAboutPanicTrigger() )
46 {
47 // no active panic triggers
48 return;
49 }
50
51 // consider every enemy, looking for the closest capable, unbusy one
52 SOLDIERTYPE* closest_enemy = NULL;
53 FOR_EACH_MERC(i)
54 {
55 SOLDIERTYPE* const pSoldier = *i;
56
57 // if this merc is unconscious, or dead
58 if (pSoldier->bLife < OKLIFE)
59 {
60 continue; // next soldier
61 }
62
63 // if this guy's too tired to go
64 if (pSoldier->bBreath < OKBREATH)
65 {
66 continue; // next soldier
67 }
68
69 if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
70 {
71 if ( pSoldier->ubProfile != WARDEN )
72 {
73 continue; // in tixa only the warden
74 }
75 }
76 else
77 {
78 if (pSoldier->bTeam != ENEMY_TEAM )
79 {
80 continue; // in other places only the army
81 }
82 }
83
84 // if this guy is in battle with opponent(s)
85 if (pSoldier->bOppCnt > 0)
86 {
87 continue; // next soldier
88 }
89
90 // if this guy is still in serious shock
91 if (pSoldier->bShock > 2)
92 {
93 continue; // next soldier
94 }
95
96 if ( pSoldier->bLevel != 0 )
97 {
98 // screw having guys on the roof go for panic triggers!
99 continue; // next soldier
100 }
101
102 bPanicTrigger = ClosestPanicTrigger( pSoldier );
103 if (bPanicTrigger == -1)
104 {
105 continue; // next soldier
106 }
107
108 sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
109 if (sPanicTriggerGridNo == NOWHERE)
110 {
111 // this should never happen!
112 continue;
113 }
114
115 // remember whether this guy had keys before
116 //const INT8 bOldKeys = pSoldier->bHasKeys;
117
118 // give him keys to see if with them he can get to the panic trigger
119 pSoldier->bHasKeys = (pSoldier->bHasKeys << 1) | 1;
120
121 // we now path directly to the panic trigger
122
123
124 // if he can't get to a spot where he could get at the panic trigger
125 /*
126 if (FindAdjacentGridEx(pSoldier, gTacticalStatus.sPanicTriggerGridno, NULL, NULL, FALSE, FALSE) == -1)
127 {
128 pSoldier->bHasKeys = bOldKeys;
129 continue; // next merc
130 }
131 */
132
133
134 // ok, this enemy appears to be eligible
135
136 // FindAdjacentGrid set HandGrid for us. If we aren't at that spot already
137 if (pSoldier->sGridNo != sPanicTriggerGridNo)
138 {
139 // get the AP cost for this enemy to go to target position
140 sPathCost = PlotPath(pSoldier, sPanicTriggerGridNo, FALSE, FALSE, WALKING, 0);
141 }
142 else
143 {
144 sPathCost = 0;
145 }
146
147 // set his keys value back to what it was before this hack
148 pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1 );
149
150 // if he can get there (or is already there!)
151 if (sPathCost || (pSoldier->sGridNo == sPanicTriggerGridNo))
152 {
153 if (sPathCost < sShortestPath)
154 {
155 sShortestPath = sPathCost;
156 closest_enemy = pSoldier;
157 }
158 }
159 }
160
161 // if we found have an eligible enemy, make him our "chosen one"
162 if (closest_enemy != NULL)
163 {
164 gTacticalStatus.the_chosen_one = closest_enemy; // flag him as the chosen one
165 if (closest_enemy->bAlertStatus < STATUS_RED)
166 {
167 closest_enemy->bAlertStatus = STATUS_RED;
168 CheckForChangingOrders(closest_enemy);
169 }
170 SetNewSituation(closest_enemy); // set new situation for the chosen one
171 closest_enemy->bHasKeys = (closest_enemy->bHasKeys << 1) | 1; // cheat and give him keys to every door
172 //closest_enemy->bHasKeys = TRUE;
173 }
174 }
175
PossiblyMakeThisEnemyChosenOne(SOLDIERTYPE * pSoldier)176 void PossiblyMakeThisEnemyChosenOne( SOLDIERTYPE * pSoldier )
177 {
178 INT32 iAPCost, iPathCost;
179 //INT8 bOldKeys;
180 INT8 bPanicTrigger;
181 INT16 sPanicTriggerGridNo;
182 UINT32 uiPercentEnemiesKilled;
183
184 if ( ! (gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) )
185 {
186 return;
187 }
188
189 if ( pSoldier->bLevel != 0 )
190 {
191 // screw having guys on the roof go for panic triggers!
192 return;
193 }
194
195
196 bPanicTrigger = ClosestPanicTrigger( pSoldier );
197 if (bPanicTrigger == -1)
198 {
199 return;
200 }
201
202 sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
203
204 uiPercentEnemiesKilled = PercentEnemiesKilled();
205 if ( gTacticalStatus.ubPanicTolerance[ bPanicTrigger ] > uiPercentEnemiesKilled )
206 {
207 // not yet... not yet
208 return;
209 }
210
211 //bOldKeys = pSoldier->bHasKeys;
212 pSoldier->bHasKeys = (pSoldier->bHasKeys << 1) | 1;
213
214 // if he can't get to a spot where he could get at the panic trigger
215 iAPCost = AP_PULL_TRIGGER;
216 if (pSoldier->sGridNo != sPanicTriggerGridNo)
217 {
218 iPathCost = PlotPath(pSoldier, sPanicTriggerGridNo, FALSE, FALSE, RUNNING, 0);
219 if (iPathCost == 0)
220 {
221 //pSoldier->bHasKeys = bOldKeys;
222 pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1);
223 return;
224 }
225 iAPCost += iPathCost;
226
227 }
228
229 if ( iAPCost <= CalcActionPoints( pSoldier ) * 2)
230 {
231 // go!!!
232 gTacticalStatus.the_chosen_one = pSoldier;
233 return;
234 }
235 // else return keys to normal
236 //pSoldier->bHasKeys = bOldKeys;
237 pSoldier->bHasKeys = (pSoldier->bHasKeys >> 1);
238 }
239
240
PanicAI(SOLDIERTYPE * pSoldier,UINT8 ubCanMove)241 INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
242 {
243 BOOLEAN fFoundRoute = FALSE;
244 INT8 bSlot;
245 INT32 iPathCost;
246 INT8 bPanicTrigger;
247 INT16 sPanicTriggerGridNo;
248
249 // if there are panic bombs here
250 if (gTacticalStatus.fPanicFlags & PANIC_BOMBS_HERE)
251 {
252 // if enemy is holding a portable panic bomb detonator, he tries to use it
253 bSlot = FindObj( pSoldier, REMOTEBOMBTRIGGER);
254 if (bSlot != NO_SLOT)
255 {
256 //////////////////////////////////////////////////////////////////////
257 // ACTIVATE DETONATOR: blow up sector's panic bombs
258 //////////////////////////////////////////////////////////////////////
259
260 // if we have enough APs to activate it now
261 if (pSoldier->bActionPoints >= AP_USE_REMOTE)
262 {
263 SLOGD("%s is activating his detonator", pSoldier->name.c_str());
264 // blow up all the PANIC bombs!
265 return(AI_ACTION_USE_DETONATOR);
266 }
267 else // otherwise, wait a turn
268 {
269 pSoldier->usActionData = NOWHERE;
270 return(AI_ACTION_NONE);
271 }
272 }
273 }
274
275 // no panic bombs, or no portable detonator
276
277 // if there's a panic trigger here (DOESN'T MATTER IF ANY PANIC BOMBS EXIST!)
278 if ( gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE )
279 {
280 // Have WE been chosen to go after the trigger?
281 if (pSoldier == gTacticalStatus.the_chosen_one)
282 {
283 bPanicTrigger = ClosestPanicTrigger( pSoldier );
284 if (bPanicTrigger == -1)
285 {
286 // augh!
287 return( -1 );
288 }
289 sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
290
291 // if not standing on the panic trigger
292 if (pSoldier->sGridNo != sPanicTriggerGridNo)
293 {
294 // determine whether we can still get there
295 iPathCost = PlotPath(pSoldier, sPanicTriggerGridNo, FALSE, FALSE, RUNNING, 0);
296 if (iPathCost != 0)
297 {
298 fFoundRoute = TRUE;
299 }
300 }
301 else
302 {
303 fFoundRoute = TRUE;
304 }
305
306 // if we managed to find an adjacent spot
307 if (fFoundRoute)
308 {
309 // if we are at that spot now
310 if (pSoldier->sGridNo == sPanicTriggerGridNo)
311 {
312 ////////////////////////////////////////////////////////////////
313 // PULL THE PANIC TRIGGER!
314 ////////////////////////////////////////////////////////////////
315
316 // and we have enough APs left to pull the trigger
317 if (pSoldier->bActionPoints >= AP_PULL_TRIGGER)
318 {
319 // blow up the all the PANIC bombs (or just the journal)
320 pSoldier->usActionData = sPanicTriggerGridNo;
321 SLOGD("%s pulls panic trigger at grid %d",
322 pSoldier->name.c_str(), pSoldier->usActionData);
323 return(AI_ACTION_PULL_TRIGGER);
324 }
325 else // otherwise, wait a turn
326 {
327 pSoldier->usActionData = NOWHERE;
328 return(AI_ACTION_NONE);
329 }
330 }
331 else // we are NOT at the HandGrid spot
332 {
333 // if we can move at least 1 square's worth
334 if (ubCanMove)
335 {
336 // if we can get to the HandGrid spot to yank the trigger
337 // animations don't allow trigger-pulling from water, so we won't!
338 if (LegalNPCDestination(pSoldier,sPanicTriggerGridNo,ENSURE_PATH,NOWATER,0))
339 {
340 pSoldier->usActionData = sPanicTriggerGridNo;
341 pSoldier->bPathStored = TRUE;
342 return(AI_ACTION_GET_CLOSER);
343 }
344 else // Oh oh, the chosen one can't get to the trigger!
345 {
346 SLOGD("!legalDest - ChosenOne can't get to the trigger!");
347 gTacticalStatus.the_chosen_one = NULL; // strip him of his Chosen One status
348 MakeClosestEnemyChosenOne(); // and replace him!
349 }
350 }
351 else // can't move, wait 1 turn
352 {
353 pSoldier->usActionData = NOWHERE;
354 return(AI_ACTION_NONE);
355 }
356 }
357 }
358 else // Oh oh, the chosen one can't get to the trigger!
359 {
360 SLOGD("!adjacentFound - ChosenOne can't get to the trigger!");
361 gTacticalStatus.the_chosen_one = NULL; // strip him of his Chosen One status
362 MakeClosestEnemyChosenOne(); // and replace him!
363 }
364 }
365 }
366
367 // no action decided
368 return(-1);
369 }
370
InitPanicSystem(void)371 void InitPanicSystem( void )
372 {
373 // start by assuming there is no panic bombs or triggers here
374 gTacticalStatus.the_chosen_one = NULL;
375 FindPanicBombsAndTriggers();
376 }
377
ClosestPanicTrigger(SOLDIERTYPE * pSoldier)378 INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier )
379 {
380 INT8 bLoop;
381 INT16 sDistance;
382 INT16 sClosestDistance = 1000;
383 INT8 bClosestTrigger = -1;
384 UINT32 uiPercentEnemiesKilled;
385
386 if (gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y)
387 {
388 if (pSoldier->ubProfile != WARDEN)
389 {
390 return -1; // in tixa only the warden
391 }
392 }
393 else
394 {
395 if (pSoldier->bTeam != ENEMY_TEAM)
396 {
397 return -1; // in other places only the army
398 }
399 }
400
401 uiPercentEnemiesKilled = PercentEnemiesKilled();
402
403 for ( bLoop = 0; bLoop < NUM_PANIC_TRIGGERS; bLoop++ )
404 {
405 if ( gTacticalStatus.sPanicTriggerGridNo[ bLoop ] != NOWHERE )
406 {
407
408 if ( gTacticalStatus.ubPanicTolerance[ bLoop ] > uiPercentEnemiesKilled )
409 {
410 // not yet... not yet...
411 continue; // next trigger
412 }
413
414 // in Tixa
415 if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
416 {
417 // screen out the second/later panic trigger if the first one hasn't been triggered
418 if ( bLoop > 0 && gTacticalStatus.sPanicTriggerGridNo[ bLoop - 1 ] != NOWHERE )
419 {
420 break;
421 }
422 }
423
424 sDistance = PythSpacesAway( pSoldier->sGridNo, gTacticalStatus.sPanicTriggerGridNo[ bLoop ] );
425 if (sDistance < sClosestDistance)
426 {
427 sClosestDistance = sDistance;
428 bClosestTrigger = bLoop;
429 }
430 }
431 }
432
433 return( bClosestTrigger );
434 }
435
NeedToRadioAboutPanicTrigger(void)436 BOOLEAN NeedToRadioAboutPanicTrigger( void )
437 {
438 UINT32 uiPercentEnemiesKilled;
439 INT8 bLoop;
440
441 if (!(gTacticalStatus.fPanicFlags & PANIC_TRIGGERS_HERE) ||
442 gTacticalStatus.the_chosen_one != NULL)
443 {
444 // already done!
445 return( FALSE );
446 }
447
448 if ( gWorldSectorX == TIXA_SECTOR_X && gWorldSectorY == TIXA_SECTOR_Y )
449 {
450 const SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(WARDEN);
451 if (!pSoldier || pSoldier == gTacticalStatus.the_chosen_one)
452 {
453 return( FALSE ); // in tixa only the warden
454 }
455 }
456 else if (!IsTeamActive(ENEMY_TEAM))
457 {
458 return FALSE; // in other places only the army
459 }
460
461 uiPercentEnemiesKilled = PercentEnemiesKilled();
462
463 for ( bLoop = 0; bLoop < NUM_PANIC_TRIGGERS; bLoop++ )
464 {
465 // if the bomb exists and its tolerance has been exceeded
466 if ( (gTacticalStatus.sPanicTriggerGridNo[ bLoop ] != NOWHERE) && ( uiPercentEnemiesKilled >= gTacticalStatus.ubPanicTolerance[ bLoop ] ) )
467 {
468 return( TRUE );
469 }
470 }
471
472 return( FALSE );
473 }
474
475
476 #define STAIRCASE_GRIDNO 12067
477 #define STAIRCASE_DIRECTION 0
478
HeadForTheStairCase(SOLDIERTYPE * pSoldier)479 INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
480 {
481 UNDERGROUND_SECTORINFO * pBasementInfo;
482
483 pBasementInfo = FindUnderGroundSector( 3, MAP_ROW_P, 1 );
484 if ( pBasementInfo && pBasementInfo->uiTimeCurrentSectorWasLastLoaded != 0 && ( pBasementInfo->ubNumElites + pBasementInfo->ubNumTroops + pBasementInfo->ubNumAdmins ) < 5 )
485 {
486 return( AI_ACTION_NONE );
487 }
488
489 if ( PythSpacesAway( pSoldier->sGridNo, STAIRCASE_GRIDNO ) < 2 )
490 {
491 return( AI_ACTION_TRAVERSE_DOWN );
492 }
493 else
494 {
495 if ( LegalNPCDestination( pSoldier, STAIRCASE_GRIDNO, ENSURE_PATH, WATEROK, 0 ) )
496 {
497 pSoldier->usActionData = STAIRCASE_GRIDNO;
498 return( AI_ACTION_GET_CLOSER );
499 }
500 }
501 return( AI_ACTION_NONE );
502 }
503
504 #define WARDEN_ALARM_GRIDNO 9376
505 #define WARDEN_GAS_GRIDNO 9216
506 // in both cases, direction 6
507