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