1 #include "AI.h"
2 #include "Animation_Control.h"
3 #include "Isometric_Utils.h"
4 #include "Weapons.h"
5 #include "OppList.h"
6 #include "Points.h"
7 #include "PathAI.h"
8 #include "WorldMan.h"
9 #include "AIInternals.h"
10 #include "Items.h"
11 #include "LOS.h"
12 #include "Assignments.h"
13 #include "Soldier_Functions.h"
14 #include "Buildings.h"
15 #include "Soldier_Macros.h"
16 #include "Render_Fun.h"
17 #include "StrategicMap.h"
18 #include "Environment.h"
19 #include "Lighting.h"
20 #include "Soldier_Create.h"
21 #include "GamePolicy.h"
22 #include "ContentManager.h"
23 #include "GameInstance.h"
24 #include "WeaponModels.h"
25 
26 //
27 // CJC's DG->JA2 conversion notes
28 //
29 // Commented out:
30 //
31 // InWaterOrGas - gas stuff
32 // RoamingRange - point patrol stuff
33 
34 UINT8 Urgency[NUM_STATUS_STATES][NUM_MORALE_STATES] =
35 {
36 	{URGENCY_LOW,  URGENCY_LOW,  URGENCY_LOW,  URGENCY_LOW,  URGENCY_LOW}, // green
37 	{URGENCY_HIGH, URGENCY_MED,  URGENCY_MED,  URGENCY_LOW,  URGENCY_LOW}, // yellow
38 	{URGENCY_HIGH, URGENCY_MED,  URGENCY_MED,  URGENCY_MED,  URGENCY_MED}, // red
39 	{URGENCY_HIGH, URGENCY_HIGH, URGENCY_HIGH, URGENCY_MED,  URGENCY_MED}  // black
40 };
41 
42 UINT16 MovementMode[LAST_MOVEMENT_ACTION + 1][NUM_URGENCY_STATES] =
43 {
44 	{WALKING,  WALKING,  WALKING }, // AI_ACTION_NONE
45 
46 	{WALKING,  WALKING,  WALKING }, // AI_ACTION_RANDOM_PATROL
47 	{WALKING,  RUNNING,  RUNNING }, // AI_ACTION_SEEK_FRIEND
48 	{WALKING,  RUNNING,  RUNNING }, // AI_ACTION_SEEK_OPPONENT
49 	{RUNNING,  RUNNING,  RUNNING }, // AI_ACTION_TAKE_COVER
50 	{WALKING,  RUNNING,  RUNNING }, // AI_ACTION_GET_CLOSER
51 
52 	{WALKING,  WALKING,  WALKING }, // AI_ACTION_POINT_PATROL,
53 	{WALKING,  RUNNING,  RUNNING }, // AI_ACTION_LEAVE_WATER_GAS,
54 	{WALKING,  SWATTING, RUNNING }, // AI_ACTION_SEEK_NOISE,
55 	{RUNNING,  RUNNING,  RUNNING }, // AI_ACTION_ESCORTED_MOVE,
56 	{WALKING,  RUNNING,  RUNNING }, // AI_ACTION_RUN_AWAY,
57 
58 	{RUNNING,  RUNNING,  RUNNING }, // AI_ACTION_KNIFE_MOVE
59 	{WALKING,  WALKING,  WALKING }, // AI_ACTION_APPROACH_MERC
60 	{RUNNING,  RUNNING,  RUNNING }, // AI_ACTION_TRACK
61 	{RUNNING,  RUNNING,  RUNNING },	// AI_ACTION_EAT
62 	{WALKING,  RUNNING,  SWATTING},	// AI_ACTION_PICKUP_ITEM
63 
64 	{WALKING,  WALKING,  WALKING},	// AI_ACTION_SCHEDULE_MOVE
65 	{WALKING,  WALKING,  WALKING},	// AI_ACTION_WALK
66 	{RUNNING,  RUNNING,  RUNNING},	// AI_ACTION_RUN
67 	{WALKING,  RUNNING,  RUNNING},	// AI_ACTION_MOVE_TO_CLIMB //extrapolated
68 
69 	{WALKING,  RUNNING,  RUNNING},	// AI_ACTION_CHANGE_FACING //extrapolated
70 	{WALKING,  RUNNING,  RUNNING},	// AI_ACTION_CHANGE_STANCE //extrapolated
71 
72 };
73 
OKToAttack(const SOLDIERTYPE * pSoldier,int target)74 INT8 OKToAttack(const SOLDIERTYPE * pSoldier, int target)
75 {
76 	// can't shoot yourself
77 	if (target == pSoldier->sGridNo)
78 		return(NOSHOOT_MYSELF);
79 
80 	if (WaterTooDeepForAttacks(pSoldier->sGridNo))
81 		return(NOSHOOT_WATER);
82 
83 	// make sure a weapon is in hand (FEB.8 ADDITION: tossable items are also OK)
84 	if (!WeaponInHand(pSoldier))
85 	{
86 		return(NOSHOOT_NOWEAPON);
87 	}
88 
89 	// JUST PUT THIS IN ON JULY 13 TO TRY AND FIX OUT-OF-AMMO SITUATIONS
90 
91 	if ( GCM->getItem(pSoldier->inv[HANDPOS].usItem)->getItemClass() == IC_GUN)
92 	{
93 		if ( pSoldier->inv[HANDPOS].usItem == TANK_CANNON )
94 		{
95 			// look for another tank shell ELSEWHERE IN INVENTORY
96 			if ( FindLaunchable( pSoldier, TANK_CANNON ) == NO_SLOT )
97 			{
98 				return(NOSHOOT_NOLOAD);
99 			}
100 		}
101 		else if (pSoldier->inv[HANDPOS].ubGunShotsLeft == 0)
102 		{
103 			return(NOSHOOT_NOAMMO);
104 		}
105 	}
106 	else if (GCM->getItem(pSoldier->inv[HANDPOS].usItem)->getItemClass() == IC_LAUNCHER)
107 	{
108 		if ( FindLaunchable( pSoldier, pSoldier->inv[HANDPOS].usItem ) == NO_SLOT )
109 		{
110 			return(NOSHOOT_NOLOAD);
111 		}
112 	}
113 
114 	return(TRUE);
115 }
116 
117 
ConsiderProne(SOLDIERTYPE * pSoldier)118 static BOOLEAN ConsiderProne(SOLDIERTYPE* pSoldier)
119 {
120 	INT16 sOpponentGridNo;
121 	INT8  bOpponentLevel;
122 	INT32 iRange;
123 
124 	if (pSoldier->bAIMorale >= MORALE_NORMAL)
125 	{
126 		return( FALSE );
127 	}
128 	// We don't want to go prone if there is a nearby enemy
129 	ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel );
130 	iRange = GetRangeFromGridNoDiff( pSoldier->sGridNo, sOpponentGridNo );
131 	if (iRange > 10)
132 	{
133 		return( TRUE );
134 	}
135 	else
136 	{
137 		return( FALSE );
138 	}
139 }
140 
StanceChange(SOLDIERTYPE * pSoldier,UINT8 ubAttackAPCost)141 UINT8 StanceChange( SOLDIERTYPE * pSoldier, UINT8 ubAttackAPCost )
142 {
143 	// consider crouching or going prone
144 
145 	if (PTR_STANDING)
146 	{
147 		if (pSoldier->bActionPoints - ubAttackAPCost >= AP_CROUCH)
148 		{
149 			if ( (pSoldier->bActionPoints - ubAttackAPCost >= AP_CROUCH + AP_PRONE) && IsValidStance( pSoldier, ANIM_PRONE ) && ConsiderProne( pSoldier ) )
150 			{
151 				return( ANIM_PRONE );
152 			}
153 			else if ( IsValidStance( pSoldier, ANIM_CROUCH ) )
154 			{
155 				return( ANIM_CROUCH );
156 			}
157 		}
158 	}
159 	else if (PTR_CROUCHED)
160 	{
161 		if ( (pSoldier->bActionPoints - ubAttackAPCost >= AP_PRONE) && IsValidStance( pSoldier, ANIM_PRONE ) && ConsiderProne( pSoldier ) )
162 		{
163 			return( ANIM_PRONE );
164 		}
165 	}
166 	return( 0 );
167 }
168 
ShootingStanceChange(SOLDIERTYPE * pSoldier,ATTACKTYPE * pAttack,INT8 bDesiredDirection)169 UINT8 ShootingStanceChange( SOLDIERTYPE * pSoldier, ATTACKTYPE * pAttack, INT8 bDesiredDirection )
170 {
171 	// Figure out the best stance for this attack
172 
173 	// We don't want to go through a lot of complex calculations here,
174 	// just compare the chance of the bullet hitting if we are
175 	// standing, crouched, or prone
176 
177 	UINT16 usRealAnimState, usBestAnimState;
178 	INT8   bBestStanceDiff=-1;
179 	INT8   bLoop, bStanceNum, bStanceDiff, bAPsAfterAttack;
180 	UINT32 uiChanceOfDamage, uiBestChanceOfDamage, uiCurrChanceOfDamage;
181 	UINT32 uiStanceBonus, uiMinimumStanceBonusPerChange = 20 - 3 * pAttack->ubAimTime;
182 	INT32  iRange;
183 
184 	bStanceNum = 0;
185 	uiCurrChanceOfDamage = 0;
186 
187 	bAPsAfterAttack = pSoldier->bActionPoints - pAttack->ubAPCost - GetAPsToReadyWeapon( pSoldier, pSoldier->usAnimState );
188 	if (bAPsAfterAttack < AP_CROUCH)
189 	{
190 		return( 0 );
191 	}
192 	// Unfortunately, to get this to work, we have to fake the AI guy's
193 	// animation state so we get the right height values
194 	usRealAnimState = pSoldier->usAnimState;
195 	usBestAnimState = pSoldier->usAnimState;
196 	uiBestChanceOfDamage = 0;
197 	iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, pAttack->sTarget );
198 
199 	switch( gAnimControl[usRealAnimState].ubEndHeight )
200 	{
201 		// set a stance number comparable with our loop variable so we can easily compute
202 		// stance differences and thus AP cost
203 		case ANIM_STAND:
204 			bStanceNum = 0;
205 			break;
206 		case ANIM_CROUCH:
207 			bStanceNum = 1;
208 			break;
209 		case ANIM_PRONE:
210 			bStanceNum = 2;
211 			break;
212 	}
213 	for (bLoop = 0; bLoop < 3; bLoop++)
214 	{
215 		bStanceDiff = ABS( bLoop - bStanceNum );
216 		if (bStanceDiff == 2 && bAPsAfterAttack < AP_CROUCH + AP_PRONE)
217 		{
218 			// can't consider this!
219 			continue;
220 		}
221 
222 		switch( bLoop )
223 		{
224 			case 0:
225 				if ( !InternalIsValidStance( pSoldier, bDesiredDirection, ANIM_STAND ) )
226 				{
227 					continue;
228 				}
229 				pSoldier->usAnimState = STANDING;
230 				break;
231 			case 1:
232 				if ( !InternalIsValidStance( pSoldier, bDesiredDirection, ANIM_CROUCH ) )
233 				{
234 					continue;
235 				}
236 				pSoldier->usAnimState = CROUCHING;
237 				break;
238 			default:
239 				if ( !InternalIsValidStance( pSoldier, bDesiredDirection, ANIM_PRONE ) )
240 				{
241 					continue;
242 				}
243 				pSoldier->usAnimState = PRONE;
244 				break;
245 		}
246 
247 		uiChanceOfDamage = SoldierToLocationChanceToGetThrough(pSoldier, pAttack->sTarget, pSoldier->bTargetLevel, pSoldier->bTargetCubeLevel, pAttack->opponent) * CalcChanceToHitGun(pSoldier, pAttack->sTarget, pAttack->ubAimTime, AIM_SHOT_TORSO, false) / 100;
248 		if (uiChanceOfDamage > 0)
249 		{
250 			uiStanceBonus = 0;
251 			// artificially augment "chance of damage" to reflect penalty to be shot at various stances
252 			switch( pSoldier->usAnimState )
253 			{
254 				case CROUCHING:
255 					if (iRange > POINT_BLANK_RANGE + 10 * (AIM_PENALTY_TARGET_CROUCHED / 3))
256 					{
257 						uiStanceBonus = AIM_BONUS_CROUCHING;
258 					}
259 					else if (iRange > POINT_BLANK_RANGE)
260 					{
261 						// reduce chance to hit with distance to the prone/immersed target
262 						uiStanceBonus = 3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE); // penalty -3%/tile
263 					}
264 					break;
265 				case PRONE:
266 					if (iRange <= MIN_PRONE_RANGE)
267 					{
268 						// HATE being prone this close!
269 						uiChanceOfDamage = 0;
270 					}
271 					else //if (iRange > POINT_BLANK_RANGE)
272 					{
273 						// reduce chance to hit with distance to the prone/immersed target
274 						uiStanceBonus = 3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE); // penalty -3%/tile
275 					}
276 					break;
277 				default:
278 					break;
279 			}
280 			// reduce stance bonus according to how much we have to change stance to get there
281 			//uiStanceBonus = uiStanceBonus * (4 - bStanceDiff) / 4;
282 			uiChanceOfDamage += uiStanceBonus;
283 		}
284 
285 		if (bStanceDiff == 0)
286 		{
287 			uiCurrChanceOfDamage = uiChanceOfDamage;
288 		}
289 		if (uiChanceOfDamage > uiBestChanceOfDamage )
290 		{
291 			uiBestChanceOfDamage = uiChanceOfDamage;
292 			usBestAnimState = pSoldier->usAnimState;
293 			bBestStanceDiff = bStanceDiff;
294 		}
295 	}
296 
297 	pSoldier->usAnimState = usRealAnimState;
298 
299 	// return 0 or the best height value to be at
300 	if (bBestStanceDiff == 0 || ((uiBestChanceOfDamage - uiCurrChanceOfDamage) / bBestStanceDiff) < uiMinimumStanceBonusPerChange)
301 	{
302 		// better off not changing our stance!
303 		return( 0 );
304 	}
305 	else
306 	{
307 		return( gAnimControl[ usBestAnimState ].ubEndHeight );
308 	}
309 }
310 
311 
DetermineMovementMode(SOLDIERTYPE * pSoldier,INT8 bAction)312 UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
313 {
314 	if ( pSoldier->fUIMovementFast )
315 	{
316 		return( RUNNING );
317 	}
318 	else if ( CREATURE_OR_BLOODCAT( pSoldier ) )
319 	{
320 		if (pSoldier->bAlertStatus == STATUS_GREEN)
321 		{
322 			return( WALKING );
323 		}
324 		else
325 		{
326 			return( RUNNING );
327 		}
328 	}
329 	else if (pSoldier->ubBodyType == COW || pSoldier->ubBodyType == CROW)
330 	{
331 		return( WALKING );
332 	}
333 	else
334 	{
335 		if ( (pSoldier->fAIFlags & AI_CAUTIOUS) && (MovementMode[bAction][Urgency[pSoldier->bAlertStatus][pSoldier->bAIMorale]] == RUNNING) )
336 		{
337 			return( WALKING );
338 		}
339 		else if ( bAction == AI_ACTION_SEEK_NOISE && pSoldier->bTeam == CIV_TEAM && !IS_MERC_BODY_TYPE( pSoldier ) )
340 		{
341 			return( WALKING );
342 		}
343 		else if ( (pSoldier->ubBodyType == HATKIDCIV || pSoldier->ubBodyType == KIDCIV) && (pSoldier->bAlertStatus == STATUS_GREEN) && Random( 10 ) == 0 )
344 		{
345 			return( KID_SKIPPING );
346 		}
347 		else
348 		{
349 			return( MovementMode[bAction][Urgency[pSoldier->bAlertStatus][pSoldier->bAIMorale]] );
350 		}
351 	}
352 }
353 
NewDest(SOLDIERTYPE * pSoldier,UINT16 usGridNo)354 void NewDest(SOLDIERTYPE *pSoldier, UINT16 usGridNo)
355 {
356 	// ATE: Setting sDestination? Tis does not make sense...
357 	//pSoldier->sDestination = usGridNo;
358 	BOOLEAN fSet = FALSE;
359 
360 	if ( IS_MERC_BODY_TYPE( pSoldier ) && pSoldier->bAction == AI_ACTION_TAKE_COVER && (pSoldier->bOrders == DEFENSIVE || pSoldier->bOrders == CUNNINGSOLO || pSoldier->bOrders == CUNNINGAID ) && (SoldierDifficultyLevel( pSoldier ) >= 2) )
361 	{
362 		UINT16 usMovementMode;
363 
364 		// getting real movement anim for someone who is going to take cover, not just considering
365 		usMovementMode = MovementMode[AI_ACTION_TAKE_COVER][Urgency[pSoldier->bAlertStatus][pSoldier->bAIMorale]];
366 		if ( usMovementMode != SWATTING )
367 		{
368 			// really want to look at path, see how far we could get on path while swatting
369 			if ( EnoughPoints( pSoldier, RecalculatePathCost( pSoldier, SWATTING ), 0, FALSE ) || (pSoldier->bLastAction == AI_ACTION_TAKE_COVER && pSoldier->usUIMovementMode == SWATTING ) )
370 			{
371 				pSoldier->usUIMovementMode = SWATTING;
372 			}
373 			else
374 			{
375 				pSoldier->usUIMovementMode = usMovementMode;
376 			}
377 		}
378 		else
379 		{
380 			pSoldier->usUIMovementMode = usMovementMode;
381 		}
382 		fSet = TRUE;
383 	}
384 	else
385 	{
386 		if ( pSoldier->bTeam == ENEMY_TEAM && pSoldier->bAlertStatus == STATUS_RED )
387 		{
388 			// switch( pSoldier->bAction )
389 			{
390 				/*
391 				case AI_ACTION_MOVE_TO_CLIMB:
392 				case AI_ACTION_RUN_AWAY:
393 					pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, pSoldier->bAction );
394 					fSet = TRUE;
395 					break;*/
396 				// default:
397 					if ( PreRandom( 5 - SoldierDifficultyLevel( pSoldier ) ) == 0 )
398 					{
399 						INT16 sClosestNoise = (INT16) MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL );
400 						if ( sClosestNoise != NOWHERE && PythSpacesAway( pSoldier->sGridNo, sClosestNoise ) < MaxDistanceVisible() + 10 )
401 						{
402 							pSoldier->usUIMovementMode = SWATTING;
403 							fSet = TRUE;
404 						}
405 					}
406 					if ( !fSet )
407 					{
408 						pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, pSoldier->bAction );
409 						fSet = TRUE;
410 					}
411 					// break;
412 			}
413 
414 		}
415 		else
416 		{
417 			pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, pSoldier->bAction );
418 			fSet = TRUE;
419 		}
420 
421 		if ( pSoldier->usUIMovementMode == SWATTING && !IS_MERC_BODY_TYPE( pSoldier ) )
422 		{
423 			pSoldier->usUIMovementMode = WALKING;
424 		}
425 	}
426 
427 	//EVENT_GetNewSoldierPath( pSoldier, pSoldier->sDestination, pSoldier->usUIMovementMode );
428 	// ATE: Using this more versitile version
429 	// Last paramater says whether to re-start the soldier's animation
430 	// This should be done if buddy was paused for fNoApstofinishMove...
431 	EVENT_InternalGetNewSoldierPath( pSoldier, usGridNo, pSoldier->usUIMovementMode , FALSE, pSoldier->fNoAPToFinishMove );
432 
433 }
434 
435 
IsActionAffordable(SOLDIERTYPE * pSoldier)436 BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier)
437 {
438 	INT8	bMinPointsNeeded = 0;
439 
440 	switch (pSoldier->bAction)
441 	{
442 		case AI_ACTION_NONE:                  // maintain current position & facing
443 			// no cost for doing nothing!
444 			break;
445 
446 		case AI_ACTION_CHANGE_FACING:         // turn to face another direction
447 			bMinPointsNeeded = (INT8) GetAPsToLook( pSoldier );
448 			break;
449 
450 		case AI_ACTION_RANDOM_PATROL:         // move towards a particular location
451 		case AI_ACTION_SEEK_FRIEND:           // move towards friend in trouble
452 		case AI_ACTION_SEEK_OPPONENT:         // move towards a reported opponent
453 		case AI_ACTION_TAKE_COVER:            // run for nearest cover from threat
454 		case AI_ACTION_GET_CLOSER:            // move closer to a strategic location
455 		case AI_ACTION_POINT_PATROL:          // move towards next patrol point
456 		case AI_ACTION_LEAVE_WATER_GAS:       // seek nearest spot of ungassed land
457 		case AI_ACTION_SEEK_NOISE:            // seek most important noise heard
458 		case AI_ACTION_ESCORTED_MOVE:         // go where told to by escortPlayer
459 		case AI_ACTION_RUN_AWAY:              // run away from nearby opponent(s)
460 		case AI_ACTION_APPROACH_MERC:
461 		case AI_ACTION_TRACK:
462 		case AI_ACTION_EAT:
463 		case AI_ACTION_SCHEDULE_MOVE:
464 		case AI_ACTION_WALK:
465 		case AI_ACTION_MOVE_TO_CLIMB:
466 			// for movement, must have enough APs to move at least 1 tile's worth
467 			bMinPointsNeeded = MinPtsToMove(pSoldier);
468 			break;
469 
470 		case AI_ACTION_PICKUP_ITEM:           // grab things lying on the ground
471 			bMinPointsNeeded = __max( MinPtsToMove( pSoldier ), AP_PICKUP_ITEM );
472 			break;
473 
474 		case AI_ACTION_OPEN_OR_CLOSE_DOOR:
475 		case AI_ACTION_UNLOCK_DOOR:
476 		case AI_ACTION_LOCK_DOOR:
477 			bMinPointsNeeded = MinPtsToMove(pSoldier);
478 			break;
479 
480 		case AI_ACTION_DROP_ITEM:
481 			bMinPointsNeeded = AP_PICKUP_ITEM;
482 			break;
483 
484 		case AI_ACTION_FIRE_GUN:              // shoot at nearby opponent
485 		case AI_ACTION_TOSS_PROJECTILE:       // throw grenade at/near opponent(s)
486 		case AI_ACTION_KNIFE_MOVE:            // preparing to stab adjacent opponent
487 		case AI_ACTION_THROW_KNIFE:
488 			// only FIRE_GUN currently actually pays extra turning costs!
489 			bMinPointsNeeded = MinAPsToAttack(pSoldier,pSoldier->usActionData,ADDTURNCOST);
490 			break;
491 
492 		case AI_ACTION_PULL_TRIGGER:          // activate an adjacent panic trigger
493 			bMinPointsNeeded = AP_PULL_TRIGGER;
494 			break;
495 
496 		case AI_ACTION_USE_DETONATOR:         // grab detonator and set off bomb(s)
497 			bMinPointsNeeded = AP_USE_REMOTE;
498 			break;
499 
500 		case AI_ACTION_YELLOW_ALERT:          // tell friends opponent(s) heard
501 		case AI_ACTION_RED_ALERT:             // tell friends opponent(s) seen
502 		case AI_ACTION_CREATURE_CALL:         // for now
503 			bMinPointsNeeded = AP_RADIO;
504 			break;
505 
506 		case AI_ACTION_CHANGE_STANCE:                // crouch
507 			bMinPointsNeeded = AP_CROUCH;
508 			break;
509 
510 		case AI_ACTION_GIVE_AID:              // help injured/dying friend
511 			bMinPointsNeeded = 0;
512 			break;
513 
514 		case AI_ACTION_CLIMB_ROOF:
515 			if (pSoldier->bLevel == 0)
516 			{
517 				bMinPointsNeeded = AP_CLIMBROOF;
518 			}
519 			else
520 			{
521 				bMinPointsNeeded = AP_CLIMBOFFROOF;
522 			}
523 			break;
524 
525 		case AI_ACTION_COWER:
526 		case AI_ACTION_STOP_COWERING:
527 		case AI_ACTION_LOWER_GUN:
528 		case AI_ACTION_END_COWER_AND_MOVE:
529 		case AI_ACTION_TRAVERSE_DOWN:
530 		case AI_ACTION_OFFER_SURRENDER:
531 			bMinPointsNeeded = 0;
532 			break;
533 
534 		default:
535 			break;
536 	}
537 
538 	// check whether or not we can afford to do this action
539 	if (bMinPointsNeeded > pSoldier->bActionPoints)
540 	{
541 		return(FALSE);
542 	}
543 	else
544 	{
545 		return(TRUE);
546 	}
547 }
548 
549 
RandomFriendWithin(SOLDIERTYPE * const s)550 INT16 RandomFriendWithin(SOLDIERTYPE* const s)
551 {
552 	// obtain maximum roaming distance from soldier's origin
553 	INT16 usOrigin;
554 	const UINT16 usMaxDist = RoamingRange(s, &usOrigin);
555 
556 	// make sure origin is a legal gridno!
557 	if (usOrigin >= GRIDSIZE)
558 	{
559 		return FALSE;
560 	}
561 
562 	// build a list of the guynums of all active, eligible friendly mercs
563 
564 	// go through each soldier, looking for "friends" (soldiers on same side)
565 	UINT8 ubFriendCount = 0;
566 	const SOLDIERTYPE* friends[MAXMERCS];
567 	FOR_EACH_MERC(i)
568 	{
569 		const SOLDIERTYPE* const candidate = *i;
570 		if (candidate == s) continue; // skip ourselves
571 
572 		/* if this man not neutral, but is on my side, OR if he is neutral, but so
573 		 * am I, then he's a "friend" for the purposes of random visitations */
574 		if ((candidate->bNeutral ? s->bNeutral : s->bSide == candidate->bSide) &&
575 				SpacesAway(s->sGridNo, candidate->sGridNo) > 1) // if we're not already neighbors
576 		{
577 			// remember his guynum, increment friend counter
578 			friends[ubFriendCount++] = candidate;
579 		}
580 	}
581 
582 	while (ubFriendCount != 0)
583 	{
584 		// randomly select one of the remaining friends in the list
585 		const UINT               chosen_idx = PreRandom(ubFriendCount);
586 		const SOLDIERTYPE* const chosen     = friends[chosen_idx];
587 
588 		/* if our movement range is NOT restricted, or this friend's within range
589 		 * use distance - 1, because there must be at least 1 tile 1 space closer */
590 		if (SpacesAway(usOrigin, chosen->sGridNo) - 1 <= usMaxDist)
591 		{
592 			// should be close enough, try to find a legal ->sDestination within 1 tile
593 
594 			BOOLEAN fDirChecked[8];
595 			// clear dirChecked flag for all 8 directions
596 			for (UINT16 usDirection = 0; usDirection < 8; ++usDirection)
597 			{
598 				fDirChecked[usDirection] = FALSE;
599 			}
600 
601 			// examine all 8 spots around friend
602 			// keep looking while directions remain and a satisfactory one not found
603 			for (UINT8 ubDirsLeft = 8; ubDirsLeft--;)
604 			{
605 				// randomly select a direction which hasn't been 'checked' yet
606 				UINT16 usDirection;
607 				do
608 				{
609 					usDirection = Random(8);
610 				}
611 				while (fDirChecked[usDirection]);
612 
613 				fDirChecked[usDirection] = TRUE;
614 
615 				// determine the gridno 1 tile away from current friend in this direction
616 				const UINT16 usDest = NewGridNo(chosen->sGridNo, DirectionInc( usDirection + 1 ));
617 
618 				// if that's out of bounds, ignore it & check next direction
619 				if (usDest == chosen->sGridNo) continue;
620 
621 				// if our movement range is NOT restricted
622 				if (SpacesAway(usOrigin,usDest) <= usMaxDist &&
623 						LegalNPCDestination(s, usDest, ENSURE_PATH, NOWATER, 0))
624 				{
625 					s->usActionData = usDest; // store this ->sDestination
626 					s->bPathStored  = TRUE;   // optimization - Ian
627 					return TRUE;
628 				}
629 			}
630 		}
631 
632 		friends[chosen_idx] = friends[--ubFriendCount];
633 	}
634 
635 	return FALSE;
636 }
637 
638 
RandDestWithinRange(SOLDIERTYPE * pSoldier)639 INT16 RandDestWithinRange(SOLDIERTYPE *pSoldier)
640 {
641 	INT16 sRandDest = NOWHERE;
642 	UINT8	ubTriesLeft;
643 	BOOLEAN fLimited = FALSE, fFound = FALSE;
644 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXRange, sYRange, sXOffset, sYOffset;
645 	INT16 sOrigX, sOrigY;
646 	INT16 sX, sY;
647 
648 	sOrigX = sOrigY = -1;
649 	sMaxLeft = sMaxRight = sMaxUp = sMaxDown = sXRange = sYRange = -1;
650 
651 	// Try to find a random ->sDestination that's no more than maxDist away from
652 	// the given gridno of origin
653 
654 	if (gfTurnBasedAI)
655 	{
656 		ubTriesLeft = 10;
657 	}
658 	else
659 	{
660 		ubTriesLeft = 1;
661 	}
662 
663 	INT16  usOrigin;
664 	UINT16 usMaxDist = RoamingRange(pSoldier,&usOrigin);
665 
666 	UINT8 room = NO_ROOM;
667 	if ( pSoldier->bOrders <= CLOSEPATROL && (pSoldier->bTeam == CIV_TEAM || pSoldier->ubProfile != NO_PROFILE ) )
668 	{
669 		// any other combo uses the default of room == NO_ROOM, set above
670 		room = GetRoom(pSoldier->usPatrolGrid[0]);
671 	}
672 
673 	// if the maxDist is truly a restriction
674 	if (usMaxDist < (MAXCOL - 1))
675 	{
676 		fLimited = TRUE;
677 
678 		// determine maximum horizontal limits
679 		sOrigX = usOrigin % MAXCOL;
680 		sOrigY = usOrigin / MAXCOL;
681 
682 		sMaxLeft  = MIN(usMaxDist, sOrigX);
683 		sMaxRight = MIN(usMaxDist,MAXCOL - (sOrigX + 1));
684 
685 		// determine maximum vertical limits
686 		sMaxUp   = MIN(usMaxDist, sOrigY);
687 		sMaxDown = MIN(usMaxDist,MAXROW - (sOrigY + 1));
688 
689 		sXRange = sMaxLeft + sMaxRight + 1;
690 		sYRange = sMaxUp + sMaxDown + 1;
691 	}
692 
693 	if (pSoldier->ubBodyType == LARVAE_MONSTER)
694 	{
695 		// only crawl 1 tile, within our roaming range
696 		while ((ubTriesLeft--) && !fFound)
697 		{
698 			sXOffset = (INT16) Random( 3 ) - 1; // generates -1 to +1
699 			sYOffset = (INT16) Random( 3 ) - 1;
700 
701 			if (fLimited)
702 			{
703 				sX = pSoldier->sGridNo % MAXCOL + sXOffset;
704 				sY = pSoldier->sGridNo / MAXCOL + sYOffset;
705 				if (sX < sOrigX - sMaxLeft || sX > sOrigX + sMaxRight)
706 				{
707 					continue;
708 				}
709 				if (sY < sOrigY - sMaxUp || sY > sOrigY + sMaxDown)
710 				{
711 					continue;
712 				}
713 				sRandDest = usOrigin + sXOffset + (MAXCOL * sYOffset);
714 			}
715 			else
716 			{
717 				sRandDest = usOrigin + sXOffset + (MAXCOL * sYOffset);
718 			}
719 
720 			if (!LegalNPCDestination(pSoldier,sRandDest,ENSURE_PATH,NOWATER,0))
721 			{
722 				sRandDest = NOWHERE;
723 				continue;                   // try again!
724 			}
725 
726 			// passed all the tests, ->sDestination is acceptable
727 			fFound = TRUE;
728 			pSoldier->bPathStored = TRUE;	// optimization - Ian
729 		}
730 	}
731 	else
732 	{
733 		// keep rolling random ->sDestinations until one's satisfactory or retries used
734 		while ((ubTriesLeft--) && !fFound)
735 		{
736 			if (fLimited)
737 			{
738 				sXOffset = ((INT16)Random(sXRange)) - sMaxLeft;
739 				sYOffset = ((INT16)Random(sYRange)) - sMaxUp;
740 
741 				sRandDest = usOrigin + sXOffset + (MAXCOL * sYOffset);
742 			}
743 			else
744 			{
745 				sRandDest = (INT16) PreRandom(GRIDSIZE);
746 			}
747 
748 			if (room != NO_ROOM)
749 			{
750 				UINT8 const temp_room = GetRoom(sRandDest);
751 				if (temp_room != NO_ROOM && temp_room != room)
752 				{
753 					// outside of room available for patrol!
754 					sRandDest = NOWHERE;
755 					continue;
756 				}
757 			}
758 
759 			if (!LegalNPCDestination(pSoldier,sRandDest,ENSURE_PATH,NOWATER,0))
760 			{
761 				sRandDest = NOWHERE;
762 				continue;                   // try again!
763 			}
764 
765 			// passed all the tests, ->sDestination is acceptable
766 			fFound = TRUE;
767 			pSoldier->bPathStored = TRUE;	// optimization - Ian
768 		}
769 	}
770 
771 	return(sRandDest); // defaults to NOWHERE
772 }
773 
ClosestReachableDisturbance(SOLDIERTYPE * pSoldier,UINT8 ubUnconsciousOK,BOOLEAN * pfChangeLevel)774 INT16 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK, BOOLEAN * pfChangeLevel )
775 {
776 	INT16   *psLastLoc, *pusNoiseGridNo;
777 	INT8    *pbLastLevel;
778 	INT16   sGridNo=-1;
779 	INT8    bLevel;
780 	BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE;
781 	INT32   iPathCost;
782 	INT16   sClosestDisturbance = NOWHERE;
783 	INT8    *pbNoiseLevel;
784 	INT8    *pbPersOL,*pbPublOL;
785 	INT16   sClimbGridNo;
786 
787 	// CJC: can't trace a path to every known disturbance!
788 	// for starters, try the closest one as the crow flies
789 	INT16   sClosestEnemy = NOWHERE, sDistToClosestEnemy = 1000, sDistToEnemy;
790 
791 	*pfChangeLevel = FALSE;
792 
793 	pusNoiseGridNo = &gsPublicNoiseGridno[pSoldier->bTeam];
794 	pbNoiseLevel = &gbPublicNoiseLevel[pSoldier->bTeam];
795 
796 	// hang pointers at start of this guy's personal and public opponent opplists
797 	//pbPersOL = &pSoldier->bOppList[0];
798 	//pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
799 	//psLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
800 
801 	// look through this man's personal & public opplists for opponents known
802 	INT8 bClosestLevel = 0; // XXX HACK000E
803 	FOR_EACH_MERC(i)
804 	{
805 		const SOLDIERTYPE* const pOpp = *i;
806 
807 		// if this merc is neutral/on same side, he's not an opponent
808 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide) )
809 		{
810 			continue;          // next merc
811 		}
812 
813 		pbPersOL = pSoldier->bOppList + pOpp->ubID;
814 		pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpp->ubID;
815 		psLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpp->ubID;
816 		pbLastLevel = gbLastKnownOppLevel[pSoldier->ubID] + pOpp->ubID;
817 
818 		// if this opponent is unknown personally and publicly
819 		if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
820 		{
821 			continue;          // next merc
822 		}
823 
824 		// this is possible if get here from BLACK AI in one of those rare
825 		// instances when we couldn't get a meaningful shot off at a guy in sight
826 		if ((*pbPersOL == SEEN_CURRENTLY) && (pOpp->bLife >= OKLIFE))
827 		{
828 			// don't allow this to return any valid values, this guy remains a
829 			// serious threat and the last thing we want to do is approach him!
830 			return(NOWHERE);
831 		}
832 
833 		// if personal knowledge is more up to date or at least equal
834 		if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
835 			(*pbPersOL == *pbPublOL))
836 		{
837 			// using personal knowledge, obtain opponent's "best guess" gridno
838 			sGridNo = *psLastLoc;
839 			bLevel = *pbLastLevel;
840 		}
841 		else
842 		{
843 			// using public knowledge, obtain opponent's "best guess" gridno
844 			sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpp->ubID];
845 			bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpp->ubID];
846 		}
847 
848 		// if we are standing at that gridno (!, obviously our info is old...)
849 		if (sGridNo == pSoldier->sGridNo)
850 		{
851 			continue;          // next merc
852 		}
853 
854 		if (sGridNo == NOWHERE)
855 		{
856 			// huh?
857 			continue;
858 		}
859 
860 		sDistToEnemy = PythSpacesAway( pSoldier->sGridNo, sGridNo );
861 		if (sDistToEnemy < sDistToClosestEnemy )
862 		{
863 			sClosestEnemy = sGridNo;
864 			bClosestLevel = bLevel;
865 			sDistToClosestEnemy = sDistToEnemy;
866 		}
867 
868 	}
869 
870 	if (sClosestEnemy != NOWHERE)
871 	{
872 		iPathCost = EstimatePathCostToLocation( pSoldier, sClosestEnemy, bClosestLevel, FALSE, &fClimbingNecessary, &sClimbGridNo );
873 		// if we can get there
874 		if (iPathCost != 0)
875 		{
876 			if (fClimbingNecessary)
877 			{
878 				sClosestDisturbance = sClimbGridNo;
879 			}
880 			else
881 			{
882 				sClosestDisturbance = sClosestEnemy;
883 			}
884 			fClosestClimbingNecessary = fClimbingNecessary;
885 		}
886 	}
887 
888 	// if any "misc. noise" was also heard recently
889 	if (pSoldier->sNoiseGridno != NOWHERE && pSoldier->sNoiseGridno != sClosestDisturbance)
890 	{
891 		// test this gridno, too
892 		sGridNo = pSoldier->sNoiseGridno;
893 		bLevel = pSoldier->bNoiseLevel;
894 
895 		// if we are there (at the noise gridno)
896 		if (sGridNo == pSoldier->sGridNo)
897 		{
898 			pSoldier->sNoiseGridno = NOWHERE;        // wipe it out, not useful anymore
899 			pSoldier->ubNoiseVolume = 0;
900 		}
901 		else
902 		{
903 			// get the AP cost to get to the location of the noise
904 			iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo );
905 			// if we can get there
906 			if (iPathCost != 0)
907 			{
908 				if (fClimbingNecessary)
909 				{
910 					sClosestDisturbance = sClimbGridNo;
911 				}
912 				else
913 				{
914 					sClosestDisturbance = sGridNo;
915 				}
916 				fClosestClimbingNecessary = fClimbingNecessary;
917 			}
918 		}
919 	}
920 
921 
922 	// if any PUBLIC "misc. noise" was also heard recently
923 	if (*pusNoiseGridNo != NOWHERE && *pusNoiseGridNo != sClosestDisturbance )
924 	{
925 		// test this gridno, too
926 		sGridNo = *pusNoiseGridNo;
927 		bLevel = *pbNoiseLevel;
928 
929 		// if we are not NEAR the noise gridno...
930 		if ( pSoldier->bLevel != bLevel || PythSpacesAway( pSoldier->sGridNo, sGridNo ) >= 6 || SoldierTo3DLocationLineOfSightTest( pSoldier, sGridNo, bLevel, 0, (UINT8) MaxDistanceVisible(), FALSE ) == 0 )
931 		// if we are NOT there (at the noise gridno)
932 		//	if (sGridNo != pSoldier->sGridNo)
933 		{
934 			// get the AP cost to get to the location of the noise
935 			iPathCost = EstimatePathCostToLocation( pSoldier, sGridNo, bLevel, FALSE, &fClimbingNecessary, &sClimbGridNo );
936 			// if we can get there
937 			if (iPathCost != 0)
938 			{
939 				if (fClimbingNecessary)
940 				{
941 					sClosestDisturbance = sClimbGridNo;
942 				}
943 				else
944 				{
945 					sClosestDisturbance = sGridNo;
946 				}
947 				fClosestClimbingNecessary = fClimbingNecessary;
948 			}
949 		}
950 		else
951 		{
952 			// degrade our public noise a bit
953 			*pusNoiseGridNo -= 2;
954 		}
955 	}
956 
957 	*pfChangeLevel = fClosestClimbingNecessary;
958 	return(sClosestDisturbance);
959 }
960 
961 
ClosestKnownOpponent(SOLDIERTYPE * pSoldier,INT16 * psGridNo,INT8 * pbLevel)962 INT16 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT16 * psGridNo, INT8 * pbLevel)
963 {
964 	INT16 sGridNo, sClosestOpponent = NOWHERE;
965 	INT32 iRange, iClosestRange = 1500;
966 	INT8  *pbPersOL, *pbPublOL;
967 	INT8  bLevel, bClosestLevel;
968 
969 	bClosestLevel = -1;
970 
971 
972 	// NOTE: THIS FUNCTION ALLOWS RETURN OF UNCONSCIOUS AND UNREACHABLE OPPONENTS
973 
974 	// hang pointers at start of this guy's personal and public opponent opplists
975 	pbPersOL = &pSoldier->bOppList[0];
976 	pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
977 
978 	// look through this man's personal & public opplists for opponents known
979 	FOR_EACH_MERC(i)
980 	{
981 		const SOLDIERTYPE* const pOpp = *i;
982 
983 		// if this merc is neutral/on same side, he's not an opponent
984 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide))
985 		{
986 			continue;          // next merc
987 		}
988 
989 		// Special stuff for Carmen the bounty hunter
990 		if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpp->ubProfile != SLAY)
991 		{
992 			continue;  // next opponent
993 		}
994 
995 		pbPersOL = pSoldier->bOppList + pOpp->ubID;
996 		pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpp->ubID;
997 
998 		// if this opponent is unknown personally and publicly
999 		if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
1000 		{
1001 			continue;          // next merc
1002 		}
1003 
1004 		// if personal knowledge is more up to date or at least equal
1005 		if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
1006 			(*pbPersOL == *pbPublOL))
1007 		{
1008 			// using personal knowledge, obtain opponent's "best guess" gridno
1009 			sGridNo = gsLastKnownOppLoc[pSoldier->ubID][pOpp->ubID];
1010 			bLevel = gbLastKnownOppLevel[pSoldier->ubID][pOpp->ubID];
1011 		}
1012 		else
1013 		{
1014 			// using public knowledge, obtain opponent's "best guess" gridno
1015 			sGridNo = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpp->ubID];
1016 			bLevel = gbPublicLastKnownOppLevel[pSoldier->bTeam][pOpp->ubID];
1017 		}
1018 
1019 		// if we are standing at that gridno(!, obviously our info is old...)
1020 		if (sGridNo == pSoldier->sGridNo)
1021 		{
1022 			continue;          // next merc
1023 		}
1024 
1025 		// this function is used only for turning towards closest opponent or changing stance
1026 		// as such, if they AI is in a building,
1027 		// we should ignore people who are on the roof of the same building as the AI
1028 		if ( (bLevel != pSoldier->bLevel) && SameBuilding( pSoldier->sGridNo, sGridNo ) )
1029 		{
1030 			continue;
1031 		}
1032 
1033 		// I hope this will be good enough; otherwise we need a fractional/world-units-based 2D distance function
1034 		//sRange = PythSpacesAway( pSoldier->sGridNo, sGridNo);
1035 		iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
1036 
1037 		if (iRange < iClosestRange)
1038 		{
1039 			iClosestRange = iRange;
1040 			sClosestOpponent = sGridNo;
1041 			bClosestLevel = bLevel;
1042 		}
1043 	}
1044 	if (psGridNo)
1045 	{
1046 		*psGridNo = sClosestOpponent;
1047 	}
1048 	if (pbLevel)
1049 	{
1050 		*pbLevel = bClosestLevel;
1051 	}
1052 	return( sClosestOpponent );
1053 }
1054 
ClosestSeenOpponent(SOLDIERTYPE * pSoldier,INT16 * psGridNo,INT8 * pbLevel)1055 INT16 ClosestSeenOpponent(SOLDIERTYPE *pSoldier, INT16 * psGridNo, INT8 * pbLevel)
1056 {
1057 	INT16 sGridNo, sClosestOpponent = NOWHERE;
1058 	INT32 iRange, iClosestRange = 1500;
1059 	INT8  *pbPersOL;
1060 	INT8  bLevel, bClosestLevel;
1061 
1062 	bClosestLevel = -1;
1063 
1064 	// look through this man's personal & public opplists for opponents known
1065 	FOR_EACH_MERC(i)
1066 	{
1067 		const SOLDIERTYPE* const pOpp = *i;
1068 
1069 		// if this merc is neutral/on same side, he's not an opponent
1070 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide))
1071 		{
1072 			continue;          // next merc
1073 		}
1074 
1075 		// Special stuff for Carmen the bounty hunter
1076 		if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpp->ubProfile != SLAY)
1077 		{
1078 			continue;  // next opponent
1079 		}
1080 
1081 		pbPersOL = pSoldier->bOppList + pOpp->ubID;
1082 
1083 		// if this opponent is not seen personally
1084 		if (*pbPersOL != SEEN_CURRENTLY)
1085 		{
1086 			continue;          // next merc
1087 		}
1088 
1089 		// since we're dealing with seen people, use exact gridnos
1090 		sGridNo = pOpp->sGridNo;
1091 		bLevel = pOpp->bLevel;
1092 
1093 		// if we are standing at that gridno(!, obviously our info is old...)
1094 		if (sGridNo == pSoldier->sGridNo)
1095 		{
1096 			continue;          // next merc
1097 		}
1098 
1099 		// this function is used only for turning towards closest opponent or changing stance
1100 		// as such, if they AI is in a building,
1101 		// we should ignore people who are on the roof of the same building as the AI
1102 		if ( (bLevel != pSoldier->bLevel) && SameBuilding( pSoldier->sGridNo, sGridNo ) )
1103 		{
1104 			continue;
1105 		}
1106 
1107 		// I hope this will be good enough; otherwise we need a fractional/world-units-based 2D distance function
1108 		//sRange = PythSpacesAway( pSoldier->sGridNo, sGridNo);
1109 		iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
1110 
1111 		if (iRange < iClosestRange)
1112 		{
1113 			iClosestRange = iRange;
1114 			sClosestOpponent = sGridNo;
1115 			bClosestLevel = bLevel;
1116 		}
1117 	}
1118 	if (psGridNo)
1119 	{
1120 		*psGridNo = sClosestOpponent;
1121 	}
1122 	if (pbLevel)
1123 	{
1124 		*pbLevel = bClosestLevel;
1125 	}
1126 	return( sClosestOpponent );
1127 }
1128 
1129 
ClosestPC(const SOLDIERTYPE * pSoldier,INT16 * psDistance)1130 INT16 ClosestPC(const SOLDIERTYPE* pSoldier, INT16* psDistance)
1131 {
1132 	// used by NPCs... find the closest PC
1133 
1134 	// NOTE: skips EPCs!
1135 
1136 	INT16 sMinDist = (INT16)WORLD_MAX;
1137 	INT16 sDist;
1138 	INT16 sGridNo = NOWHERE;
1139 
1140 	CFOR_EACH_IN_TEAM(pTargetSoldier, OUR_TEAM)
1141 	{
1142 		if (!pTargetSoldier->bInSector)
1143 		{
1144 			continue;
1145 		}
1146 
1147 		// if not conscious, skip him
1148 		if (pTargetSoldier->bLife < OKLIFE)
1149 		{
1150 			continue;
1151 		}
1152 
1153 		if ( AM_AN_EPC( pTargetSoldier ) )
1154 		{
1155 			continue;
1156 		}
1157 
1158 		sDist = PythSpacesAway(pSoldier->sGridNo,pTargetSoldier->sGridNo);
1159 
1160 		// if this PC is not visible to the soldier, then add a penalty to the distance
1161 		// so that we weight in favour of visible mercs
1162 		if (pTargetSoldier->bTeam != pSoldier->bTeam && pSoldier->bOppList[pTargetSoldier->ubID] != SEEN_CURRENTLY)
1163 		{
1164 			sDist += 10;
1165 		}
1166 
1167 		if (sDist < sMinDist)
1168 		{
1169 			sMinDist = sDist;
1170 			sGridNo = pTargetSoldier->sGridNo;
1171 		}
1172 	}
1173 
1174 	if ( psDistance )
1175 	{
1176 		*psDistance = sMinDist;
1177 	}
1178 
1179 	return( sGridNo );
1180 }
1181 
1182 
FindClosestClimbPointAvailableToAI(SOLDIERTYPE * pSoldier,INT16 sStartGridNo,INT16 sDesiredGridNo,BOOLEAN fClimbUp)1183 static INT16 FindClosestClimbPointAvailableToAI(SOLDIERTYPE* pSoldier, INT16 sStartGridNo, INT16 sDesiredGridNo, BOOLEAN fClimbUp)
1184 {
1185 	INT16 sGridNo;
1186 	INT16 sRoamingOrigin;
1187 	INT16 sRoamingRange;
1188 
1189 	if ( pSoldier->uiStatusFlags & SOLDIER_PC )
1190 	{
1191 		sRoamingOrigin = pSoldier->sGridNo;
1192 		sRoamingRange = 99;
1193 	}
1194 	else
1195 	{
1196 		sRoamingRange = RoamingRange( pSoldier, &sRoamingOrigin );
1197 	}
1198 
1199 	//if the enemy is on the roof currently
1200 	if (gamepolicy(stay_on_rooftop) && pSoldier->bLevel == 1)
1201 	{
1202 		//if the soldier is currently on close patrol or on guard
1203 		if (pSoldier->bOrders == ONGUARD || pSoldier->bOrders == CLOSEPATROL)
1204 		{
1205 			//Make it so he cant climb down off the roof
1206 			STLOGD("TacticalAI: soldier #{} is on guard ({}) and not allowed to climb down", pSoldier->ubID, pSoldier->bOrders);
1207 			return NOWHERE;
1208 		}
1209 	}
1210 
1211 	// since climbing necessary involves going an extra tile, we compare against 1 less than the roam range...
1212 	// or add 1 to the distance to the climb point
1213 
1214 	sGridNo = FindClosestClimbPoint( sStartGridNo, sDesiredGridNo, fClimbUp );
1215 
1216 
1217 	if ( PythSpacesAway( sRoamingOrigin, sGridNo ) + 1 > sRoamingRange )
1218 	{
1219 		return( NOWHERE );
1220 	}
1221 	else
1222 	{
1223 		return( sGridNo );
1224 	}
1225 }
1226 
ClimbingNecessary(SOLDIERTYPE * pSoldier,INT16 sDestGridNo,INT8 bDestLevel)1227 BOOLEAN ClimbingNecessary( SOLDIERTYPE * pSoldier, INT16 sDestGridNo, INT8 bDestLevel )
1228 {
1229 	if (pSoldier->bLevel == bDestLevel)
1230 	{
1231 		if ( (pSoldier->bLevel == 0) || ( gubBuildingInfo[ pSoldier->sGridNo ] == gubBuildingInfo[ sDestGridNo ] ) )
1232 		{
1233 			return( FALSE );
1234 		}
1235 		else // different buildings!
1236 		{
1237 			return( TRUE );
1238 		}
1239 	}
1240 	else
1241 	{
1242 		return( TRUE );
1243 	}
1244 }
1245 
GetInterveningClimbingLocation(SOLDIERTYPE * pSoldier,INT16 sDestGridNo,INT8 bDestLevel,BOOLEAN * pfClimbingNecessary)1246 INT16 GetInterveningClimbingLocation( SOLDIERTYPE * pSoldier, INT16 sDestGridNo, INT8 bDestLevel, BOOLEAN * pfClimbingNecessary )
1247 {
1248 	if (pSoldier->bLevel == bDestLevel)
1249 	{
1250 		if ( (pSoldier->bLevel == 0) || ( gubBuildingInfo[ pSoldier->sGridNo ] == gubBuildingInfo[ sDestGridNo ] ) )
1251 		{
1252 			// on ground or same building... normal!
1253 			*pfClimbingNecessary = FALSE;
1254 			return( NOWHERE );
1255 		}
1256 		else
1257 		{
1258 			// different buildings!
1259 			// yes, pass in same gridno twice... want closest climb-down spot for building we are on!
1260 			*pfClimbingNecessary = TRUE;
1261 			return( FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE ) );
1262 		}
1263 	}
1264 	else
1265 	{
1266 		*pfClimbingNecessary = TRUE;
1267 		// different levels
1268 		if (pSoldier->bLevel == 0)
1269 		{
1270 			// got to go UP onto building
1271 			return( FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, sDestGridNo, TRUE ) );
1272 		}
1273 		else
1274 		{
1275 			// got to go DOWN off building
1276 			return( FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE ) );
1277 		}
1278 	}
1279 }
1280 
EstimatePathCostToLocation(SOLDIERTYPE * pSoldier,INT16 sDestGridNo,INT8 bDestLevel,BOOLEAN fAddCostAfterClimbingUp,BOOLEAN * pfClimbingNecessary,INT16 * psClimbGridNo)1281 INT16 EstimatePathCostToLocation( SOLDIERTYPE * pSoldier, INT16 sDestGridNo, INT8 bDestLevel, BOOLEAN fAddCostAfterClimbingUp, BOOLEAN * pfClimbingNecessary, INT16 * psClimbGridNo )
1282 {
1283 	INT16 sPathCost;
1284 	INT16 sClimbGridNo;
1285 
1286 	if (pSoldier->bLevel == bDestLevel)
1287 	{
1288 		if ( (pSoldier->bLevel == 0) || ( gubBuildingInfo[ pSoldier->sGridNo ] == gubBuildingInfo[ sDestGridNo ] ) )
1289 		{
1290 			// on ground or same building... normal!
1291 			sPathCost = EstimatePlotPath(pSoldier, sDestGridNo, FALSE, FALSE, WALKING, 0);
1292 			*pfClimbingNecessary = FALSE;
1293 			*psClimbGridNo = NOWHERE;
1294 		}
1295 		else
1296 		{
1297 			// different buildings!
1298 			// yes, pass in same gridno twice... want closest climb-down spot for building we are on!
1299 			sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE );
1300 			if (sClimbGridNo == NOWHERE)
1301 			{
1302 				sPathCost = 0;
1303 			}
1304 			else
1305 			{
1306 				sPathCost = PlotPath(pSoldier, sClimbGridNo, FALSE, FALSE, WALKING, 0);
1307 				if (sPathCost != 0)
1308 				{
1309 					// add in cost of climbing down
1310 					if (fAddCostAfterClimbingUp)
1311 					{
1312 						// add in cost of later climbing up, too
1313 						sPathCost += AP_CLIMBOFFROOF + AP_CLIMBROOF;
1314 						// add in an estimate of getting there after climbing down
1315 						sPathCost += (AP_MOVEMENT_FLAT + WALKCOST) * PythSpacesAway( sClimbGridNo, sDestGridNo );
1316 					}
1317 					else
1318 					{
1319 						sPathCost += AP_CLIMBOFFROOF;
1320 						// add in an estimate of getting there after climbing down, *but not on top of roof*
1321 						sPathCost += (AP_MOVEMENT_FLAT + WALKCOST) * PythSpacesAway( sClimbGridNo, sDestGridNo ) / 2;
1322 					}
1323 					*pfClimbingNecessary = TRUE;
1324 					*psClimbGridNo = sClimbGridNo;
1325 				}
1326 			}
1327 		}
1328 	}
1329 	else
1330 	{
1331 		// different levels
1332 		if (pSoldier->bLevel == 0)
1333 		{
1334 			//got to go UP onto building
1335 			sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier,  pSoldier->sGridNo, sDestGridNo, TRUE );
1336 		}
1337 		else
1338 		{
1339 			// got to go DOWN off building
1340 			sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE );
1341 		}
1342 
1343 		if (sClimbGridNo == NOWHERE)
1344 		{
1345 			sPathCost = 0;
1346 		}
1347 		else
1348 		{
1349 			sPathCost = PlotPath(pSoldier, sClimbGridNo, FALSE, FALSE, WALKING, 0);
1350 			if (sPathCost != 0)
1351 			{
1352 				// add in the cost of climbing up or down
1353 				if (pSoldier->bLevel == 0)
1354 				{
1355 					// must climb up
1356 					sPathCost += AP_CLIMBROOF;
1357 					if (fAddCostAfterClimbingUp)
1358 					{
1359 						// add to path a rough estimate of how far to go from the climb gridno to the friend
1360 						// estimate walk cost
1361 						sPathCost += (AP_MOVEMENT_FLAT + WALKCOST) * PythSpacesAway( sClimbGridNo, sDestGridNo );
1362 					}
1363 				}
1364 				else
1365 				{
1366 					// must climb down
1367 					sPathCost += AP_CLIMBOFFROOF;
1368 					// add to path a rough estimate of how far to go from the climb gridno to the friend
1369 					// estimate walk cost
1370 					sPathCost += (AP_MOVEMENT_FLAT + WALKCOST) * PythSpacesAway( sClimbGridNo, sDestGridNo );
1371 				}
1372 				*pfClimbingNecessary = TRUE;
1373 				*psClimbGridNo = sClimbGridNo;
1374 			}
1375 		}
1376 	}
1377 
1378 	return( sPathCost );
1379 }
1380 
1381 
GuySawEnemyThisTurnOrBefore(SOLDIERTYPE * pSoldier)1382 static BOOLEAN GuySawEnemyThisTurnOrBefore(SOLDIERTYPE* pSoldier)
1383 {
1384 	UINT8 ubTeamLoop;
1385 	UINT8 ubIDLoop;
1386 
1387 	for ( ubTeamLoop = 0; ubTeamLoop < MAXTEAMS; ubTeamLoop++ )
1388 	{
1389 		if ( gTacticalStatus.Team[ ubTeamLoop ].bSide != pSoldier->bSide )
1390 		{
1391 			// consider guys in this team, which isn't on our side
1392 			for ( ubIDLoop = gTacticalStatus.Team[ ubTeamLoop ].bFirstID; ubIDLoop <= gTacticalStatus.Team[ ubTeamLoop ].bLastID; ubIDLoop++ )
1393 			{
1394 				// if this guy SAW an enemy recently...
1395 				if ( pSoldier->bOppList[ ubIDLoop ] >= SEEN_CURRENTLY )
1396 				{
1397 					return( TRUE );
1398 				}
1399 			}
1400 		}
1401 	}
1402 
1403 	return( FALSE );
1404 }
1405 
ClosestReachableFriendInTrouble(SOLDIERTYPE * pSoldier,BOOLEAN * pfClimbingNecessary)1406 INT16 ClosestReachableFriendInTrouble(SOLDIERTYPE *pSoldier, BOOLEAN * pfClimbingNecessary)
1407 {
1408 	INT16 sPathCost, sClosestFriend = NOWHERE, sShortestPath = 1000, sClimbGridNo;
1409 	BOOLEAN fClimbingNecessary, fClosestClimbingNecessary = FALSE;
1410 
1411 	// civilians don't really have any "friends", so they don't bother with this
1412 	if (IsOnCivTeam(pSoldier)) return sClosestFriend;
1413 
1414 	// consider every friend of this soldier (locations assumed to be known)
1415 	FOR_EACH_MERC(i)
1416 	{
1417 		SOLDIERTYPE* const pFriend = *i;
1418 
1419 		// if this merc is neutral or NOT on the same side, he's not a friend
1420 		if (pFriend->bNeutral || (pSoldier->bSide != pFriend->bSide))
1421 		{
1422 			continue;          // next merc
1423 		}
1424 
1425 		// if this "friend" is actually US
1426 		if (pFriend == pSoldier) continue; // next merc
1427 
1428 		// CJC: restrict "last one to radio" to only if that guy saw us this turn or last turn
1429 
1430 		// if this friend is not under fire, and isn't the last one to radio
1431 		if (!pFriend->bUnderFire &&
1432 			(pFriend != gTacticalStatus.Team[pFriend->bTeam].last_merc_to_radio || !GuySawEnemyThisTurnOrBefore(pFriend)))
1433 		{
1434 			continue;          // next merc
1435 		}
1436 
1437 		// if we're already neighbors
1438 		if (SpacesAway(pSoldier->sGridNo,pFriend->sGridNo) == 1)
1439 		{
1440 			continue;          // next merc
1441 		}
1442 
1443 		// get the AP cost to go to this friend's gridno
1444 		sPathCost = EstimatePathCostToLocation( pSoldier, pFriend->sGridNo, pFriend->bLevel, TRUE, &fClimbingNecessary, &sClimbGridNo );
1445 
1446 		// if we can get there
1447 		if (sPathCost != 0)
1448 		{
1449 			if (sPathCost < sShortestPath)
1450 			{
1451 				if (fClimbingNecessary)
1452 				{
1453 					sClosestFriend = sClimbGridNo;
1454 				}
1455 				else
1456 				{
1457 					sClosestFriend = pFriend->sGridNo;
1458 				}
1459 
1460 				sShortestPath = sPathCost;
1461 				fClosestClimbingNecessary = fClimbingNecessary;
1462 			}
1463 		}
1464 	}
1465 	*pfClimbingNecessary = fClosestClimbingNecessary;
1466 	return(sClosestFriend);
1467 }
1468 
DistanceToClosestFriend(SOLDIERTYPE * pSoldier)1469 INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier )
1470 {
1471 	// find the distance to the closest person on the same team
1472 	INT16 sMinDist = 1000;
1473 	INT16 sDist;
1474 
1475 	CFOR_EACH_IN_TEAM(pTargetSoldier, pSoldier->bTeam)
1476 	{
1477 		if (pTargetSoldier == pSoldier) continue;
1478 
1479 		if (pSoldier->bInSector)
1480 		{
1481 			if (!pTargetSoldier->bInSector)
1482 			{
1483 				continue;
1484 			}
1485 			// if not conscious, skip him
1486 			else if (pTargetSoldier->bLife < OKLIFE)
1487 			{
1488 				continue;
1489 			}
1490 		}
1491 		else
1492 		{
1493 			// compare sector #s
1494 			if ( (pSoldier->sSectorX != pTargetSoldier->sSectorX) ||
1495 				(pSoldier->sSectorY != pTargetSoldier->sSectorY) ||
1496 				(pSoldier->bSectorZ != pTargetSoldier->bSectorZ) )
1497 			{
1498 				continue;
1499 			}
1500 			else if (pTargetSoldier->bLife < OKLIFE)
1501 			{
1502 				continue;
1503 			}
1504 			else
1505 			{
1506 				// well there's someone who could be near
1507 				return( 1 );
1508 			}
1509 		}
1510 
1511 		sDist = SpacesAway(pSoldier->sGridNo,pTargetSoldier->sGridNo);
1512 
1513 		if (sDist < sMinDist)
1514 		{
1515 			sMinDist = sDist;
1516 		}
1517 	}
1518 
1519 	return( sMinDist );
1520 }
1521 
1522 
InSmoke(SOLDIERTYPE const * const s,GridNo const gridno)1523 static bool InSmoke(SOLDIERTYPE const* const s, GridNo const gridno)
1524 {
1525 	return gpWorldLevelData[gridno].ubExtFlags[s->bLevel] & MAPELEMENT_EXT_SMOKE;
1526 }
1527 
1528 
InWaterGasOrSmoke(SOLDIERTYPE const * const s,GridNo const gridno)1529 bool InWaterGasOrSmoke(SOLDIERTYPE const* const s, GridNo const gridno)
1530 {
1531 	return WaterTooDeepForAttacks(gridno) || InSmoke(s, gridno) || InGas(s, gridno);
1532 }
1533 
1534 
InGasOrSmoke(SOLDIERTYPE const * const s,GridNo const gridno)1535 bool InGasOrSmoke(SOLDIERTYPE const* const s, GridNo const gridno)
1536 {
1537 	return InSmoke(s, gridno) || InGas(s, gridno);
1538 }
1539 
1540 
InWaterOrGas(SOLDIERTYPE const * const s,GridNo const grid_no)1541 bool InWaterOrGas(SOLDIERTYPE const* const s, GridNo const grid_no)
1542 {
1543 	return WaterTooDeepForAttacks(grid_no) || InGas(s, grid_no);
1544 }
1545 
1546 
InGas(SOLDIERTYPE const * const s,GridNo const grid_no)1547 bool InGas(SOLDIERTYPE const* const s, GridNo const grid_no)
1548 {
1549 	return
1550 		gpWorldLevelData[grid_no].ubExtFlags[s->bLevel] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) &&
1551 		!IsWearingHeadGear(*s, GASMASK);
1552 }
1553 
1554 
WearGasMaskIfAvailable(SOLDIERTYPE * const s)1555 bool WearGasMaskIfAvailable(SOLDIERTYPE* const s)
1556 {
1557 	INT8 const slot = FindObj(s, GASMASK);
1558 	if (slot == NO_SLOT || slot == HEAD1POS || slot == HEAD2POS) return false;
1559 
1560 	INT8 const new_slot =
1561 		s->inv[HEAD1POS].usItem == NOTHING ? HEAD1POS :
1562 		s->inv[HEAD2POS].usItem == NOTHING ? HEAD2POS :
1563 		HEAD1POS; // Screw it, going in position 1 anyhow
1564 
1565 	RearrangePocket(s, slot, new_slot, TRUE);
1566 	return true;
1567 }
1568 
1569 
InLightAtNight(INT16 sGridNo,INT8 bLevel)1570 BOOLEAN InLightAtNight( INT16 sGridNo, INT8 bLevel )
1571 {
1572 	UINT8 ubBackgroundLightLevel;
1573 
1574 	// do not consider us to be "in light" if we're in an underground sector
1575 	if ( gbWorldSectorZ > 0 )
1576 	{
1577 		return( FALSE );
1578 	}
1579 
1580 	ubBackgroundLightLevel = GetTimeOfDayAmbientLightLevel();
1581 
1582 	if ( ubBackgroundLightLevel < NORMAL_LIGHTLEVEL_DAY + 2 )
1583 	{
1584 		// don't consider it nighttime, too close to daylight levels
1585 		return( FALSE );
1586 	}
1587 
1588 	// could've been placed here, ignore the light
1589 	if (GetRoom(sGridNo) != NO_ROOM) return FALSE;
1590 
1591 	// NB light levels are backwards, so a lower light level means the
1592 	// spot in question is BRIGHTER
1593 	return LightTrueLevel(sGridNo, bLevel) < ubBackgroundLightLevel;
1594 }
1595 
CalcMorale(SOLDIERTYPE * pSoldier)1596 INT8 CalcMorale(SOLDIERTYPE *pSoldier)
1597 {
1598 	INT32 iOurTotalThreat = 0, iTheirTotalThreat = 0;
1599 	INT16 sOppThreatValue, sFrndThreatValue, sMorale;
1600 	INT32 iPercent;
1601 	INT8  bMostRecentOpplistValue;
1602 	INT8  bMoraleCategory;
1603 	INT8  *pbPersOL, *pbPublOL;
1604 
1605 	// if army guy has NO weapons left then panic!
1606 	if ( pSoldier->bTeam == ENEMY_TEAM )
1607 	{
1608 		if ( FindAIUsableObjClass( pSoldier, IC_WEAPON ) == NO_SLOT )
1609 		{
1610 			return( MORALE_HOPELESS );
1611 		}
1612 	}
1613 
1614 	// hang pointers to my personal opplist, my team's public opplist, and my
1615 	// list of previously seen opponents
1616 	const INT8* pSeenOpp = &gbSeenOpponents[pSoldier->ubID][0];
1617 
1618 	// loop through every one of my possible opponents
1619 	FOR_EACH_MERC(i)
1620 	{
1621 		SOLDIERTYPE* const pOpponent = *i;
1622 
1623 		// if this merc is inactive, at base, on assignment, dead, unconscious
1624 		if (pOpponent->bLife < OKLIFE) continue; // next merc
1625 
1626 		// if this merc is neutral/on same side, he's not an opponent, skip him!
1627 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
1628 			continue;          // next merc
1629 
1630 		// Special stuff for Carmen the bounty hunter
1631 		if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
1632 		{
1633 			continue;  // next opponent
1634 		}
1635 
1636 		pbPersOL = pSoldier->bOppList + pOpponent->ubID;
1637 		pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
1638 		pSeenOpp = gbSeenOpponents[pSoldier->ubID] + pOpponent->ubID;
1639 
1640 		// if this opponent is unknown to me personally AND unknown to my team, too
1641 		if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
1642 		{
1643 			// if I have never seen him before anywhere in this sector, either
1644 			if (!(*pSeenOpp))
1645 				continue;        // next merc
1646 
1647 			// have seen him in the past, so he remains something of a threat
1648 			bMostRecentOpplistValue = 0;        // uses the free slot for 0 opplist
1649 		}
1650 		else         // decide which opplist is more current
1651 		{
1652 			// if personal knowledge is more up to date or at least equal
1653 			if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) || (*pbPersOL == *pbPublOL))
1654 				bMostRecentOpplistValue = *pbPersOL;      // use personal
1655 			else
1656 				bMostRecentOpplistValue = *pbPublOL;      // use public
1657 		}
1658 
1659 		iPercent = ThreatPercent[bMostRecentOpplistValue - OLDEST_HEARD_VALUE];
1660 
1661 		sOppThreatValue = (iPercent * CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier)) / 100;
1662 
1663 		// ADD this to their running total threatValue (decreases my MORALE)
1664 		iTheirTotalThreat += sOppThreatValue;
1665 
1666 		// NOW THE FUN PART: SINCE THIS OPPONENT IS KNOWN TO ME IN SOME WAY,
1667 		// ANY FRIENDS OF MINE THAT KNOW ABOUT HIM BOOST MY MORALE.  SO, LET'S GO
1668 		// THROUGH THEIR PERSONAL OPPLISTS AND CHECK WHICH OF MY FRIENDS KNOW
1669 		// SOMETHING ABOUT HIM AND WHAT THEIR THREAT VALUE TO HIM IS.
1670 
1671 		FOR_EACH_MERC(j)
1672 		{
1673 			SOLDIERTYPE* const pFriend = *j;
1674 
1675 			// if this merc is inactive, at base, on assignment, dead, unconscious
1676 			if (pFriend->bLife < OKLIFE) continue; // next merc
1677 
1678 			// if this merc is not on my side, then he's NOT one of my friends
1679 
1680 			// WE CAN'T AFFORD TO CONSIDER THE ENEMY OF MY ENEMY MY FRIEND, HERE!
1681 			// ONLY IF WE ARE ACTUALLY OFFICIALLY CO-OPERATING TOGETHER (SAME SIDE)
1682 			if ( pFriend->bNeutral && !( pSoldier->ubCivilianGroup != NON_CIV_GROUP && pSoldier->ubCivilianGroup == pFriend->ubCivilianGroup ) )
1683 			{
1684 				continue;        // next merc
1685 			}
1686 
1687 			if ( pSoldier->bSide != pFriend->bSide )
1688 				continue;        // next merc
1689 
1690 			// THIS TEST IS INVALID IF A COMPUTER-TEAM IS PLAYING CO-OPERATIVELY
1691 			// WITH A NON-COMPUTER TEAM SINCE THE OPPLISTS INVOLVED ARE NOT
1692 			// UP-TO-DATE.  THIS SITUATION IS CURRENTLY NOT POSSIBLE IN HTH/DG.
1693 
1694 			// ALSO NOTE THAT WE COUNT US AS OUR (BEST) FRIEND FOR THESE CALCULATIONS
1695 
1696 			// subtract HEARD_2_TURNS_AGO (which is negative) to make values start at 0 and
1697 			// be positive otherwise
1698 			iPercent = ThreatPercent[pFriend->bOppList[pOpponent->ubID] - OLDEST_HEARD_VALUE];
1699 
1700 			// reduce the percentage value based on how far away they are from the enemy, if they only hear him
1701 			if ( pFriend->bOppList[ pOpponent->ubID ] <= HEARD_LAST_TURN )
1702 			{
1703 				iPercent -= PythSpacesAway( pSoldier->sGridNo, pFriend->sGridNo ) * 2;
1704 				if ( iPercent <= 0 )
1705 				{
1706 					//ignore!
1707 					continue;
1708 				}
1709 			}
1710 
1711 			sFrndThreatValue = (iPercent * CalcManThreatValue(pFriend,pOpponent->sGridNo,FALSE,pSoldier)) / 100;
1712 
1713 			// ADD this to our running total threatValue (increases my MORALE)
1714 			// We multiply by sOppThreatValue to PRO-RATE this based on opponent's
1715 			// threat value to ME personally.  Divide later by sum of them all.
1716 			iOurTotalThreat += sOppThreatValue * sFrndThreatValue;
1717 		}
1718 	}
1719 
1720 
1721 	// if they are no threat whatsoever
1722 	if (!iTheirTotalThreat)
1723 		sMorale = 500;        // our morale is just incredible
1724 	else
1725 	{
1726 		// now divide sOutTotalThreat by sTheirTotalThreat to get the REAL value
1727 		iOurTotalThreat /= iTheirTotalThreat;
1728 
1729 		// calculate the morale (100 is even, < 100 is us losing, > 100 is good)
1730 		sMorale = (INT16) ((100 * iOurTotalThreat) / iTheirTotalThreat);
1731 	}
1732 
1733 
1734 	if (sMorale <= 25)              // odds 1:4 or worse
1735 		bMoraleCategory = MORALE_HOPELESS;
1736 	else if (sMorale <= 50)         // odds between 1:4 and 1:2
1737 		bMoraleCategory = MORALE_WORRIED;
1738 	else if (sMorale <= 150)        // odds between 1:2 and 3:2
1739 		bMoraleCategory = MORALE_NORMAL;
1740 	else if (sMorale <= 300)        // odds between 3:2 and 3:1
1741 		bMoraleCategory = MORALE_CONFIDENT;
1742 	else                           // odds better than 3:1
1743 		bMoraleCategory = MORALE_FEARLESS;
1744 
1745 
1746 	switch (pSoldier->bAttitude)
1747 	{
1748 		case DEFENSIVE:	bMoraleCategory--; break;
1749 		case BRAVESOLO:	bMoraleCategory += 2; break;
1750 		case BRAVEAID:	bMoraleCategory += 2; break;
1751 		case CUNNINGSOLO:    break;
1752 		case CUNNINGAID:     break;
1753 		case AGGRESSIVE:	bMoraleCategory++; break;
1754 	}
1755 
1756 	// make idiot administrators much more aggressive
1757 	if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR )
1758 	{
1759 		bMoraleCategory += 2;
1760 	}
1761 
1762 
1763 	// if still full of energy
1764 	if (pSoldier->bBreath > 75)
1765 		bMoraleCategory++;
1766 	else
1767 	{
1768 		// if getting a bit low on breath
1769 		if (pSoldier->bBreath < 40)
1770 			bMoraleCategory--;
1771 
1772 		// if getting REALLY low on breath
1773 		if (pSoldier->bBreath < 10)
1774 			bMoraleCategory--;
1775 	}
1776 
1777 
1778 	// if still very healthy
1779 	if (pSoldier->bLife > 75)
1780 		bMoraleCategory++;
1781 	else
1782 	{
1783 		// if getting a bit low on life
1784 		if (pSoldier->bLife < 40)
1785 			bMoraleCategory--;
1786 
1787 		// if getting REALLY low on life
1788 		if (pSoldier->bLife < 20)
1789 			bMoraleCategory--;
1790 	}
1791 
1792 
1793 	// if soldier is currently not under fire
1794 	if (!pSoldier->bUnderFire)
1795 		bMoraleCategory++;
1796 
1797 
1798 	// if adjustments made it outside the allowed limits
1799 	if (bMoraleCategory < MORALE_HOPELESS)
1800 		bMoraleCategory = MORALE_HOPELESS;
1801 	else
1802 	{
1803 		if (bMoraleCategory > MORALE_FEARLESS)
1804 			bMoraleCategory = MORALE_FEARLESS;
1805 	}
1806 
1807 	// brave guys never get hopeless, at worst they get worried
1808 	if (bMoraleCategory == MORALE_HOPELESS &&
1809 		(pSoldier->bAttitude == BRAVESOLO || pSoldier->bAttitude == BRAVEAID))
1810 		bMoraleCategory = MORALE_WORRIED;
1811 
1812 	SLOGD("Morale = %d (category %d), iOurTotalThreat %d, iTheirTotalThreat %d",
1813 				sMorale, bMoraleCategory, iOurTotalThreat, iTheirTotalThreat);
1814 
1815 	return(bMoraleCategory);
1816 }
1817 
CalcManThreatValue(SOLDIERTYPE * pEnemy,INT16 sMyGrid,UINT8 ubReduceForCover,SOLDIERTYPE * pMe)1818 INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT16 sMyGrid, UINT8 ubReduceForCover, SOLDIERTYPE * pMe )
1819 {
1820 	INT32	iThreatValue = 0;
1821 	BOOLEAN fForCreature = CREATURE_OR_BLOODCAT( pMe );
1822 
1823 	// If man is inactive, at base, on assignment, dead, unconscious
1824 	if (!pEnemy->bActive || !pEnemy->bInSector || !pEnemy->bLife)
1825 	{
1826 		// he's no threat at all, return a negative number
1827 		iThreatValue = -999;
1828 		return(iThreatValue);
1829 	}
1830 
1831 	// in boxing mode, let only a boxer be considered a threat.
1832 	if ( (gTacticalStatus.bBoxingState == BOXING) && !(pEnemy->uiStatusFlags & SOLDIER_BOXER) )
1833 	{
1834 		iThreatValue = -999;
1835 		return( iThreatValue );
1836 	}
1837 
1838 	if (fForCreature)
1839 	{
1840 		// health (1-100)
1841 		iThreatValue += pEnemy->bLife;
1842 		// bleeding (more attactive!) (1-100)
1843 		iThreatValue += pEnemy->bBleeding;
1844 		// decrease according to distance
1845 		iThreatValue = (iThreatValue * 10) / (10 + PythSpacesAway( sMyGrid, pEnemy->sGridNo ) );
1846 
1847 	}
1848 	else
1849 	{
1850 		// ADD twice the man's level (2-20)
1851 		iThreatValue += pEnemy->bExpLevel;
1852 
1853 		// ADD man's total action points (10-35)
1854 		iThreatValue += CalcActionPoints(pEnemy);
1855 
1856 		// ADD 1/2 of man's current action points (4-17)
1857 		iThreatValue += (pEnemy->bActionPoints / 2);
1858 
1859 		// ADD 1/10 of man's current health (0-10)
1860 		iThreatValue += (pEnemy->bLife / 10);
1861 
1862 		if (pEnemy->bAssignment < ON_DUTY )
1863 		{
1864 			// ADD 1/4 of man's protection percentage (0-25)
1865 			iThreatValue += ArmourPercent( pEnemy ) / 4;
1866 
1867 			// ADD 1/5 of man's marksmanship skill (0-20)
1868 			iThreatValue += (pEnemy->bMarksmanship / 5);
1869 
1870 			if ( GCM->getItem(pEnemy->inv[HANDPOS].usItem)->isWeapon() )
1871 			{
1872 				// ADD the deadliness of the item(weapon) he's holding (0-50)
1873 				iThreatValue += GCM->getWeapon(pEnemy->inv[HANDPOS].usItem)->ubDeadliness;
1874 			}
1875 		}
1876 
1877 		// SUBTRACT 1/5 of man's bleeding (0-20)
1878 		iThreatValue -= (pEnemy->bBleeding / 5);
1879 
1880 		// SUBTRACT 1/10 of man's breath deficiency (0-10)
1881 		iThreatValue -= ((100 - pEnemy->bBreath) / 10);
1882 
1883 		// SUBTRACT man's current shock value
1884 		iThreatValue -= pEnemy->bShock;
1885 	}
1886 
1887 	// if I have a specifically defined spot where I'm at (sometime I don't!)
1888 	if (sMyGrid != NOWHERE)
1889 	{
1890 		// ADD 10% if man's already been shooting at me
1891 		if (pEnemy->sLastTarget == sMyGrid)
1892 		{
1893 			iThreatValue += (iThreatValue / 10);
1894 		}
1895 		else
1896 		{
1897 			// ADD 5% if man's already facing me
1898 			if (pEnemy->bDirection == GetDirectionToGridNoFromGridNo(pEnemy->sGridNo, sMyGrid))
1899 			{
1900 				iThreatValue += (iThreatValue / 20);
1901 			}
1902 		}
1903 	}
1904 
1905 	// if this man is conscious
1906 	if (pEnemy->bLife >= OKLIFE)
1907 	{
1908 		// and we were told to reduce threat for my cover
1909 		if (ubReduceForCover && (sMyGrid != NOWHERE))
1910 		{
1911 			// Reduce iThreatValue to same % as the chance HE has shoot through at ME
1912 			//iThreatValue = (iThreatValue * ChanceToGetThrough( pEnemy, myGrid, FAKE, ACTUAL, TESTWALLS, 9999, M9PISTOL, NOT_FOR_LOS)) / 100;
1913 			//iThreatValue = (iThreatValue * SoldierTo3DLocationChanceToGetThrough( pEnemy, myGrid, FAKE, ACTUAL, TESTWALLS, 9999, M9PISTOL, NOT_FOR_LOS)) / 100;
1914 			iThreatValue = iThreatValue * SoldierToLocationChanceToGetThrough(pEnemy, sMyGrid, pMe->bLevel, 0, pMe) / 100;
1915 		}
1916 	}
1917 	else
1918 	{
1919 		// if he's still something of a threat
1920 		if (iThreatValue > 0)
1921 		{
1922 			// drastically reduce his threat value (divide by 5 to 18)
1923 			iThreatValue /= (4 + (OKLIFE - pEnemy->bLife));
1924 		}
1925 	}
1926 
1927 	// threat value of any opponent can never drop below 1
1928 	if (iThreatValue < 1)
1929 	{
1930 		iThreatValue = 1;
1931 	}
1932 	return(iThreatValue);
1933 }
1934 
RoamingRange(SOLDIERTYPE * pSoldier,INT16 * pusFromGridNo)1935 INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT16 * pusFromGridNo)
1936 {
1937 	if ( CREATURE_OR_BLOODCAT( pSoldier ) )
1938 	{
1939 		if ( pSoldier->bAlertStatus == STATUS_BLACK )
1940 		{
1941 			*pusFromGridNo = pSoldier->sGridNo; // from current position!
1942 			return(MAX_ROAMING_RANGE);
1943 		}
1944 	}
1945 	if ( pSoldier->bOrders == POINTPATROL || pSoldier->bOrders == RNDPTPATROL )
1946 	{
1947 		// roam near NEXT PATROL POINT, not from where merc starts out
1948 		*pusFromGridNo = pSoldier->usPatrolGrid[pSoldier->bNextPatrolPnt];
1949 	}
1950 	else
1951 	{
1952 		// roam around where mercs started
1953 		//*pusFromGridNo = pSoldier->sInitialGridNo;
1954 		*pusFromGridNo = pSoldier->usPatrolGrid[0];
1955 	}
1956 
1957 	switch (pSoldier->bOrders)
1958 	{
1959 		// JA2 GOLD: give non-NPCs a 5 tile roam range for cover in combat when being shot at
1960 		case STATIONARY:
1961 			if (pSoldier->ubProfile != NO_PROFILE || (pSoldier->bAlertStatus < STATUS_BLACK && !(pSoldier->bUnderFire)))
1962 			{
1963 				return( 0 );
1964 			}
1965 			else
1966 			{
1967 				return( 5 );
1968 			}
1969 		case ONGUARD:
1970 			return( 5 );
1971 		case CLOSEPATROL:
1972 			return( 15 );
1973 		case RNDPTPATROL:
1974 		case POINTPATROL:
1975 			return(10 );     // from nextPatrolGrid, not whereIWas
1976 		case FARPATROL:
1977 			if (pSoldier->bAlertStatus < STATUS_RED)
1978 			{
1979 				return( 25 );
1980 			}
1981 			else
1982 			{
1983 				return( 50 );
1984 			}
1985 		case ONCALL:
1986 			if (pSoldier->bAlertStatus < STATUS_RED)
1987 			{
1988 				return( 10 );
1989 			}
1990 			else
1991 			{
1992 				return( 30 );
1993 			}
1994 		case SEEKENEMY:
1995 			*pusFromGridNo = pSoldier->sGridNo; // from current position!
1996 			return(MAX_ROAMING_RANGE);
1997 		default:
1998 			return(0);
1999 	}
2000 }
2001 
2002 
RearrangePocket(SOLDIERTYPE * pSoldier,INT8 bPocket1,INT8 bPocket2,UINT8 bPermanent)2003 void RearrangePocket(SOLDIERTYPE *pSoldier, INT8 bPocket1, INT8 bPocket2, UINT8 bPermanent)
2004 {
2005 	// NB there's no such thing as a temporary swap for now...
2006 	SwapObjs( &(pSoldier->inv[bPocket1]), &(pSoldier->inv[bPocket2]) );
2007 }
2008 
2009 
FindBetterSpotForItem(SOLDIERTYPE & s,INT8 const slot)2010 bool FindBetterSpotForItem(SOLDIERTYPE& s, INT8 const slot)
2011 {
2012 	/* Look for a place in the slots to put an item in a hand or armour position,
2013 	 * and move it there. */
2014 	if (slot >= BIGPOCK1POS) return false;
2015 
2016 	UINT16 const item = s.inv[slot].usItem;
2017 	if (item == NOTHING) return true; // That's just fine
2018 
2019 	INT8 new_slot;
2020 	if (GCM->getItem(item)->getPerPocket() != 0)
2021 	{ // Try a small pocket first
2022 		new_slot = FindEmptySlotWithin(&s, SMALLPOCK1POS, SMALLPOCK8POS);
2023 		if (new_slot == NO_SLOT) goto try_big_pocket;
2024 	}
2025 	else
2026 	{ // Look for a big pocket
2027 try_big_pocket:
2028 		new_slot = FindEmptySlotWithin(&s, BIGPOCK1POS, BIGPOCK4POS);
2029 		if (new_slot == NO_SLOT) return false;
2030 	}
2031 
2032 	RearrangePocket(&s, HANDPOS, new_slot, FOREVER);
2033 	return true;
2034 }
2035 
2036 
GetTraversalQuoteActionID(INT8 bDirection)2037 UINT8 GetTraversalQuoteActionID( INT8 bDirection )
2038 {
2039 	switch( bDirection )
2040 	{
2041 		case NORTHEAST: // east
2042 			return( QUOTE_ACTION_ID_TRAVERSE_EAST );
2043 
2044 		case SOUTHEAST: // south
2045 			return( QUOTE_ACTION_ID_TRAVERSE_SOUTH );
2046 
2047 		case SOUTHWEST: // west
2048 			return( QUOTE_ACTION_ID_TRAVERSE_WEST );
2049 
2050 		case NORTHWEST: // north
2051 			return( QUOTE_ACTION_ID_TRAVERSE_NORTH );
2052 
2053 		default:
2054 			return( 0 );
2055 	}
2056 }
2057 
SoldierDifficultyLevel(const SOLDIERTYPE * pSoldier)2058 UINT8 SoldierDifficultyLevel( const SOLDIERTYPE * pSoldier )
2059 {
2060 	INT8 bDifficultyBase;
2061 	INT8 bDifficulty;
2062 
2063 	// difficulty modifier ranges from 0 to 100
2064 	// and we want to end up with a number between 0 and 4 (4=hardest)
2065 	// to a base of 1, divide by 34 to get a range from 1 to 3
2066 	bDifficultyBase = 1 + ( CalcDifficultyModifier( pSoldier->ubSoldierClass ) / 34 );
2067 
2068 	switch( pSoldier->ubSoldierClass )
2069 	{
2070 		case SOLDIER_CLASS_ADMINISTRATOR:
2071 			bDifficulty = bDifficultyBase - 1;
2072 			break;
2073 
2074 		case SOLDIER_CLASS_ARMY:
2075 			bDifficulty = bDifficultyBase;
2076 			break;
2077 
2078 		case SOLDIER_CLASS_ELITE:
2079 			bDifficulty = bDifficultyBase + 1;
2080 			break;
2081 
2082 		// hard code militia;
2083 		case SOLDIER_CLASS_GREEN_MILITIA:
2084 			bDifficulty = 2;
2085 			break;
2086 
2087 		case SOLDIER_CLASS_REG_MILITIA:
2088 			bDifficulty = 3;
2089 			break;
2090 
2091 		case SOLDIER_CLASS_ELITE_MILITIA:
2092 			bDifficulty = 4;
2093 			break;
2094 
2095 		default:
2096 			if (pSoldier->bTeam == CREATURE_TEAM)
2097 			{
2098 				bDifficulty = bDifficultyBase + pSoldier->bLevel / 4;
2099 			}
2100 			else // civ...
2101 			{
2102 				bDifficulty = (bDifficultyBase + pSoldier->bLevel / 4) - 1;
2103 			}
2104 			break;
2105 
2106 	}
2107 
2108 	bDifficulty = __max( bDifficulty, 0 );
2109 	bDifficulty = __min( bDifficulty, 4 );
2110 
2111 	return( (UINT8) bDifficulty );
2112 }
2113 
ValidCreatureTurn(SOLDIERTYPE * pCreature,INT8 bNewDirection)2114 BOOLEAN ValidCreatureTurn( SOLDIERTYPE * pCreature, INT8 bNewDirection )
2115 {
2116 	INT8    bDirChange;
2117 	INT8    bTempDir;
2118 	INT8    bLoop;
2119 	BOOLEAN fFound;
2120 
2121 	bDirChange = (INT8) QuickestDirection( pCreature->bDirection, bNewDirection );
2122 
2123 	for( bLoop = 0; bLoop < 2; bLoop++ )
2124 	{
2125 		fFound = TRUE;
2126 
2127 		bTempDir = pCreature->bDirection;
2128 
2129 		do
2130 		{
2131 
2132 			bTempDir += bDirChange;
2133 			if (bTempDir < NORTH)
2134 			{
2135 				bTempDir = NORTHWEST;
2136 			}
2137 			else if (bTempDir > NORTHWEST)
2138 			{
2139 				bTempDir = NORTH;
2140 			}
2141 			if (!InternalIsValidStance( pCreature, bTempDir, ANIM_STAND ))
2142 			{
2143 				fFound = FALSE;
2144 				break;
2145 			}
2146 
2147 		} while ( bTempDir != bNewDirection );
2148 
2149 		if ( fFound )
2150 		{
2151 			break;
2152 		}
2153 		else if ( bLoop > 0 )
2154 		{
2155 			// can't find a dir!
2156 			return( FALSE );
2157 		}
2158 		else
2159 		{
2160 			// try the other direction
2161 			bDirChange = bDirChange * -1;
2162 		}
2163 	}
2164 
2165 	return( TRUE );
2166 }
2167 
RangeChangeDesire(const SOLDIERTYPE * pSoldier)2168 INT32 RangeChangeDesire( const SOLDIERTYPE * pSoldier )
2169 {
2170 	INT32 iRangeFactorMultiplier;
2171 
2172 	iRangeFactorMultiplier = pSoldier->bAIMorale - 1;
2173 	switch (pSoldier->bAttitude)
2174 	{
2175 		case DEFENSIVE:      iRangeFactorMultiplier += -1; break;
2176 		case BRAVESOLO:      iRangeFactorMultiplier +=  2; break;
2177 		case BRAVEAID:       iRangeFactorMultiplier +=  2; break;
2178 		case CUNNINGSOLO:    iRangeFactorMultiplier +=  0; break;
2179 		case CUNNINGAID:     iRangeFactorMultiplier +=  0; break;
2180 		case ATTACKSLAYONLY:
2181 		case AGGRESSIVE:     iRangeFactorMultiplier +=  1; break;
2182 	}
2183 	if ( gTacticalStatus.bConsNumTurnsWeHaventSeenButEnemyDoes > 0 )
2184 	{
2185 		iRangeFactorMultiplier += gTacticalStatus.bConsNumTurnsWeHaventSeenButEnemyDoes;
2186 	}
2187 	return( iRangeFactorMultiplier );
2188 }
2189 
ArmySeesOpponents(void)2190 BOOLEAN ArmySeesOpponents( void )
2191 {
2192 	CFOR_EACH_IN_TEAM(s, ENEMY_TEAM)
2193 	{
2194 		if (s->bInSector && s->bLife >= OKLIFE && s->bOppCnt > 0)
2195 		{
2196 			return TRUE;
2197 		}
2198 	}
2199 
2200 	return( FALSE );
2201 }
2202