1 #include "Animation_Control.h"
2 #include "Animation_Data.h"
3 #include "FindLocations.h"
4 #include "Handle_Items.h"
5 #include "Isometric_Utils.h"
6 #include "AI.h"
7 #include "AIInternals.h"
8 #include "LOS.h"
9 #include "Soldier_Profile.h"
10 #include "Structure.h"
11 #include "Weapons.h"
12 #include "OppList.h"
13 #include "PathAI.h"
14 #include "Items.h"
15 #include "World_Items.h"
16 #include "Points.h"
17 #include "Map_Edgepoints.h"
18 #include "RenderWorld.h"
19 #include "Render_Fun.h"
20 #include "Boxing.h"
21 #include "Text.h"
22 #include "Structure_Wrap.h"
23 #ifdef _DEBUG
24 	#include "Video.h"
25 #endif
26 #include "WorldMan.h"
27 #include "StrategicMap.h"
28 #include "Environment.h"
29 #include "Lighting.h"
30 #include "Debug.h"
31 #include "PathAIDebug.h"
32 
33 #include "ContentManager.h"
34 #include "GameInstance.h"
35 #include "WeaponModels.h"
36 
37 #include <algorithm>
38 
39 #ifdef _DEBUG
40 	INT16 gsCoverValue[WORLD_MAX];
41 	INT16 gsBestCover;
42 	#ifndef PATHAI_VISIBLE_DEBUG
43 	// NB Change this to true to get visible cover debug -- CJC
44 	BOOLEAN gfDisplayCoverValues = FALSE;
45 	#endif
46 #endif
47 
48 INT8 gubAIPathCosts[19][19];
49 
50 
CalcPercentBetter(INT32 iOldValue,INT32 iNewValue,INT32 iOldScale,INT32 iNewScale)51 static INT32 CalcPercentBetter(INT32 iOldValue, INT32 iNewValue, INT32 iOldScale, INT32 iNewScale)
52 {
53 	INT32 iValueChange,iScaleSum,iPercentBetter;//,loopCnt,tempInt;
54 
55 	// calcalate how much better the new cover would be than the current cover
56 	iValueChange = iNewValue - iOldValue;
57 
58 	// here, the change in cover HAS to be an improvement over current cover
59 	if (iValueChange <= 0)
60 	{
61 		return(NOWHERE);
62 	}
63 
64 	iScaleSum = iOldScale + iNewScale;
65 
66 	// here, the change in cover HAS to be an improvement over current cover
67 	if (iScaleSum <= 0)
68 	{
69 		return(NOWHERE);
70 	}
71 	iPercentBetter = (iValueChange * 100) / iScaleSum;
72 	SLOGD(ST::format("CalcPercentBetter: %Better {}, old {}, new {}, change {}\noldScale {}, newScale {}, scaleSum {}",
73 				iPercentBetter, iOldValue, iNewValue, iValueChange, iOldScale,
74 				iNewScale, iScaleSum));
75 	return(iPercentBetter);
76 }
77 
78 
AICenterXY(INT16 sGridNo,FLOAT * pdX,FLOAT * pdY)79 static void AICenterXY(INT16 sGridNo, FLOAT* pdX, FLOAT* pdY)
80 {
81 	INT16 sXPos, sYPos;
82 
83 	sXPos = sGridNo % WORLD_COLS;
84 	sYPos = sGridNo / WORLD_COLS;
85 
86 	*pdX = (FLOAT) (sXPos * CELL_X_SIZE + CELL_X_SIZE / 2);
87 	*pdY = (FLOAT) (sYPos * CELL_Y_SIZE + CELL_Y_SIZE / 2);
88 }
89 
90 
CalcWorstCTGTForPosition(SOLDIERTYPE * const pSoldier,const SOLDIERTYPE * const opponent,const INT16 sOppGridNo,const INT8 bLevel,const INT32 iMyAPsLeft)91 static INT8 CalcWorstCTGTForPosition(SOLDIERTYPE* const pSoldier, const SOLDIERTYPE* const opponent, const INT16 sOppGridNo, const INT8 bLevel, const INT32 iMyAPsLeft)
92 {
93 	// When considering a gridno for cover, we want to take into account cover if we
94 	// lie down, so we return the LOWEST chance to get through for that location.
95 	INT8 bCubeLevel, bThisCTGT,bWorstCTGT = 100;
96 
97 	for (bCubeLevel = 1; bCubeLevel <= 3; bCubeLevel++)
98 	{
99 		switch (bCubeLevel)
100 		{
101 			case 1:
102 				if (iMyAPsLeft < AP_CROUCH + AP_PRONE)
103 				{
104 					continue;
105 				}
106 				break;
107 			case 2:
108 				if (iMyAPsLeft < AP_CROUCH)
109 				{
110 					continue;
111 				}
112 				break;
113 			default:
114 				break;
115 		}
116 
117 		bThisCTGT = SoldierToLocationChanceToGetThrough(pSoldier, sOppGridNo, bLevel, bCubeLevel, opponent);
118 		if (bThisCTGT < bWorstCTGT)
119 		{
120 			bWorstCTGT = bThisCTGT;
121 			// if there is perfect cover
122 			if (bWorstCTGT == 0)
123 				// then bail from the for loop, it can't possible get any better
124 				break;
125 		}
126 	}
127 	return( bWorstCTGT );
128 }
129 
130 
CalcAverageCTGTForPosition(SOLDIERTYPE * const pSoldier,const SOLDIERTYPE * const opponent,const INT16 sOppGridNo,const INT8 bLevel,const INT32 iMyAPsLeft)131 static INT8 CalcAverageCTGTForPosition(SOLDIERTYPE* const pSoldier, const SOLDIERTYPE* const opponent, const INT16 sOppGridNo, const INT8 bLevel, const INT32 iMyAPsLeft)
132 {
133 	// When considering a gridno for cover, we want to take into account cover if we
134 	// lie down, so we return the LOWEST chance to get through for that location.
135 	INT8  bCubeLevel;
136 	INT32 iTotalCTGT       = 0;
137 	INT32 bValidCubeLevels = 0;
138 
139 	for (bCubeLevel = 1; bCubeLevel <= 3; bCubeLevel++)
140 	{
141 		switch (bCubeLevel)
142 		{
143 			case 1:
144 				if (iMyAPsLeft < AP_CROUCH + AP_PRONE)
145 				{
146 					continue;
147 				}
148 				break;
149 			case 2:
150 				if (iMyAPsLeft < AP_CROUCH)
151 				{
152 					continue;
153 				}
154 				break;
155 			default:
156 				break;
157 		}
158 		iTotalCTGT += SoldierToLocationChanceToGetThrough(pSoldier, sOppGridNo, bLevel, bCubeLevel, opponent);
159 		bValidCubeLevels++;
160 	}
161 	iTotalCTGT /= bValidCubeLevels;
162 	return( (INT8) iTotalCTGT );
163 }
164 
165 
CalcBestCTGT(SOLDIERTYPE * const pSoldier,const SOLDIERTYPE * const opponent,const INT16 sOppGridNo,const INT8 bLevel,const INT32 iMyAPsLeft)166 static INT8 CalcBestCTGT(SOLDIERTYPE* const pSoldier, const SOLDIERTYPE* const opponent, const INT16 sOppGridNo, const INT8 bLevel, const INT32 iMyAPsLeft)
167 {
168 	// NOTE: CTGT stands for "ChanceToGetThrough..."
169 
170 	// using only ints for maximum execution speed here
171 	// CJC: Well, so much for THAT idea!
172 	INT16 sCentralGridNo, sAdjSpot, sNorthGridNo, sSouthGridNo, sDir, sCheckSpot;
173 
174 	INT8 bThisCTGT, bBestCTGT = 0;
175 
176 	sCheckSpot = -1;
177 
178 	sCentralGridNo = pSoldier->sGridNo;
179 
180 	// precalculate these for speed
181 	// what was struct for?
182 	sNorthGridNo = NewGridNo( sCentralGridNo, DirectionInc(NORTH) );
183 	sSouthGridNo = NewGridNo( sCentralGridNo, DirectionInc(SOUTH) );
184 
185 	// look into all 8 adjacent tiles & determine where the cover is the worst
186 	for (sDir = 1; sDir <= 8; sDir++)
187 	{
188 		// get the gridno of the adjacent spot lying in that direction
189 		sAdjSpot = NewGridNo( sCentralGridNo, DirectionInc( sDir ) );
190 
191 		// if it wasn't out of bounds
192 		if (sAdjSpot != sCentralGridNo)
193 		{
194 			// if the adjacent spot can we walked on and isn't in water or gas
195 			if ((NewOKDestination( pSoldier, sAdjSpot, IGNOREPEOPLE, bLevel ) > 0) && !InWaterOrGas( pSoldier, sAdjSpot ))
196 			{
197 				switch (sDir)
198 				{
199 					case NORTH:
200 					case EAST:
201 					case SOUTH:
202 					case WEST:
203 						sCheckSpot = sAdjSpot;
204 						break;
205 					case NORTHEAST:
206 					case NORTHWEST:
207 						// spot to the NORTH is guaranteed to be in bounds since NE/NW was
208 						sCheckSpot = sNorthGridNo;
209 						break;
210 					case SOUTHEAST:
211 					case SOUTHWEST:
212 						// spot to the SOUTH is guaranteed to be in bounds since SE/SW was
213 						sCheckSpot = sSouthGridNo;
214 						break;
215 				}
216 
217 				// ATE: OLD STUFF
218 				// if the adjacent gridno is reachable from the starting spot
219 				if ( NewOKDestination( pSoldier, sCheckSpot, FALSE, bLevel ) )
220 				{
221 					// the dude could move to this adjacent gridno, so put him there
222 					// "virtually" so we can calculate what our cover is from there
223 
224 					// NOTE: GOTTA SET THESE 3 FIELDS *BACK* AFTER USING THIS FUNCTION!!!
225 					pSoldier->sGridNo = sAdjSpot;     // pretend he's standing at 'sAdjSpot'
226 					AICenterXY( sAdjSpot, &(pSoldier->dXPos), &(pSoldier->dYPos) );
227 					bThisCTGT = CalcWorstCTGTForPosition(pSoldier, opponent, sOppGridNo, bLevel, iMyAPsLeft);
228 					if (bThisCTGT > bBestCTGT)
229 					{
230 						bBestCTGT = bThisCTGT;
231 						// if there is no cover
232 						if (bBestCTGT == 100)
233 							// then bail from the for loop, it can't possible get any better
234 							break;
235 					}
236 				}
237 			}
238 		}
239 	}
240 
241 	return( bBestCTGT );
242 }
243 
244 
CalcCoverValue(SOLDIERTYPE * pMe,INT16 sMyGridNo,INT32 iMyThreat,INT32 iMyAPsLeft,UINT32 uiThreatIndex,INT32 iRange,INT32 morale,INT32 * iTotalScale)245 static INT32 CalcCoverValue(SOLDIERTYPE* pMe, INT16 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft, UINT32 uiThreatIndex, INT32 iRange, INT32 morale, INT32* iTotalScale)
246 {
247 	// all 32-bit integers for max. speed
248 	INT32 iMyPosValue, iHisPosValue, iCoverValue;
249 	INT32 iReductionFactor, iThisScale;
250 	INT16 sHisGridNo, sMyRealGridNo = NOWHERE, sHisRealGridNo = NOWHERE;
251 	INT16 sTempX, sTempY;
252 	FLOAT dMyX, dMyY, dHisX, dHisY;
253 	INT8  bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
254 	INT32 iRangeChange, iRangeFactor, iRangeFactorMultiplier;
255 	SOLDIERTYPE *pHim;
256 
257 	dMyX = dMyY = dHisX = dHisY = -1.0;
258 
259 	pHim = Threat[uiThreatIndex].pOpponent;
260 	sHisGridNo = Threat[uiThreatIndex].sGridNo;
261 
262 	// THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK.  IF YOU REALLY
263 	// HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO
264 
265 	// if this is theoretical, and I'm not actually at sMyGridNo right now
266 	if (pMe->sGridNo != sMyGridNo)
267 	{
268 		sMyRealGridNo = pMe->sGridNo;        // remember where I REALLY am
269 		dMyX = pMe->dXPos;
270 		dMyY = pMe->dYPos;
271 
272 		pMe->sGridNo = sMyGridNo;              // but pretend I'm standing at sMyGridNo
273 		ConvertGridNoToCenterCellXY( sMyGridNo, &sTempX, &sTempY );
274 		pMe->dXPos = (FLOAT) sTempX;
275 		pMe->dYPos = (FLOAT) sTempY;
276 	}
277 
278 	// if this is theoretical, and he's not actually at hisGrid right now
279 	if (pHim->sGridNo != sHisGridNo)
280 	{
281 		sHisRealGridNo = pHim->sGridNo;      // remember where he REALLY is
282 		dHisX = pHim->dXPos;
283 		dHisY = pHim->dYPos;
284 
285 		pHim->sGridNo = sHisGridNo;            // but pretend he's standing at sHisGridNo
286 		ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY );
287 		pHim->dXPos = (FLOAT) sTempX;
288 		pHim->dYPos = (FLOAT) sTempY;
289 	}
290 
291 
292 	if (InWaterOrGas(pHim,sHisGridNo))
293 	{
294 		bHisActualCTGT = 0;
295 	}
296 	else
297 	{
298 		// optimistically assume we'll be behind the best cover available at this spot
299 
300 		//bHisActualCTGT = ChanceToGetThrough(pHim,sMyGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
301 		bHisActualCTGT = CalcWorstCTGTForPosition(pHim, pMe, sMyGridNo, pMe->bLevel, iMyAPsLeft);
302 	}
303 
304 	// normally, that will be the cover I'll use, unless worst case over-rides it
305 	bHisCTGT = bHisActualCTGT;
306 
307 	// only calculate his best case CTGT if there is room for improvement!
308 	if (bHisActualCTGT < 100)
309 	{
310 		// if we didn't remember his real gridno earlier up above, we got to now,
311 		// because calculating worst case is about to play with it in a big way!
312 		if (sHisRealGridNo == NOWHERE)
313 		{
314 			sHisRealGridNo = pHim->sGridNo;      // remember where he REALLY is
315 			dHisX = pHim->dXPos;
316 			dHisY = pHim->dYPos;
317 		}
318 
319 		// calculate where my cover is worst if opponent moves just 1 tile over
320 		bHisBestCTGT = CalcBestCTGT(pHim, pMe, sMyGridNo, pMe->bLevel, iMyAPsLeft);
321 
322 		// if he can actually improve his CTGT by moving to a nearby gridno
323 		if (bHisBestCTGT > bHisActualCTGT)
324 		{
325 			// he may not take advantage of his best case, so take only 2/3 of best
326 			bHisCTGT = ((2 * bHisBestCTGT) + bHisActualCTGT) / 3;
327 		}
328 	}
329 
330 	// if my intended gridno is in water or gas, I can't attack at all from there
331 	// here, for smoke, consider bad
332 	if (InWaterGasOrSmoke(pMe,sMyGridNo))
333 	{
334 		bMyCTGT = 0;
335 	}
336 	else
337 	{
338 		// put him at sHisGridNo if necessary!
339 		if (pHim->sGridNo != sHisGridNo )
340 		{
341 			pHim->sGridNo = sHisGridNo;
342 			ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY );
343 			pHim->dXPos = (FLOAT) sTempX;
344 			pHim->dYPos = (FLOAT) sTempY;
345 		}
346 		// bMyCTGT = ChanceToGetThrough(pMe,sHisGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
347 		// bMyCTGT = SoldierToLocationChanceToGetThrough( pMe, sHisGridNo, pMe->bTargetLevel, pMe->bTargetCubeLevel );
348 
349 		// let's not assume anything about the stance the enemy might take, so take an average
350 		// value... no cover give a higher value than partial cover
351 		bMyCTGT = CalcAverageCTGTForPosition(pMe, pHim, sHisGridNo, pHim->bLevel, iMyAPsLeft);
352 
353 		// since NPCs are too dumb to shoot "blind", ie. at opponents that they
354 		// themselves can't see (mercs can, using another as a spotter!), if the
355 		// cover is below the "see_thru" threshold, it's equivalent to perfect cover!
356 		if (bMyCTGT < SEE_THRU_COVER_THRESHOLD)
357 		{
358 			bMyCTGT = 0;
359 		}
360 	}
361 
362 	// UNDO ANY TEMPORARY "DAMAGE" DONE ABOVE
363 	if (sMyRealGridNo != NOWHERE)
364 	{
365 		pMe->sGridNo = sMyRealGridNo;        // put me back where I belong!
366 		pMe->dXPos = dMyX;                      // also change the 'x'
367 		pMe->dYPos = dMyY;                      // and the 'y'
368 	}
369 
370 	if (sHisRealGridNo != NOWHERE)
371 	{
372 		pHim->sGridNo = sHisRealGridNo;      // put HIM back where HE belongs!
373 		pHim->dXPos = dHisX;                    // also change the 'x'
374 		pHim->dYPos = dHisY;                    // and the 'y'
375 	}
376 
377 
378 	// these value should be < 1 million each
379 	iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs;
380 	iMyPosValue =  bMyCTGT *  iMyThreat * iMyAPsLeft;
381 
382 
383 	// try to account for who outnumbers who: the side with the advantage thus
384 	// (hopefully) values offense more, while those in trouble will play defense
385 	if (pHim->bOppCnt > 1)
386 	{
387 		iHisPosValue /= pHim->bOppCnt;
388 	}
389 
390 	if (pMe->bOppCnt > 1)
391 	{
392 		iMyPosValue /= pMe->bOppCnt;
393 	}
394 
395 
396 	// if my positional value is worth something at all here
397 	if (iMyPosValue > 0)
398 	{
399 		// if I CAN'T crouch when I get there, that makes it significantly less
400 		// appealing a spot (how much depends on range), so that's a penalty to me
401 		if (iMyAPsLeft < AP_CROUCH)
402 			// subtract another 1 % penalty for NOT being able to crouch per tile
403 			// the farther away we are, the bigger a difference crouching will make!
404 			iMyPosValue -= ((iMyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100);
405 	}
406 
407 
408 	// high morale prefers decreasing the range (positive factor), while very
409 	// low morale (HOPELESS) prefers increasing it
410 
411 	//if (bHisCTGT < 100 || (morale - 1 < 0))
412 	{
413 
414 		iRangeFactorMultiplier = RangeChangeDesire( pMe );
415 
416 		if (iRangeFactorMultiplier)
417 		{
418 			iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
419 
420 			if (iRangeChange)
421 			{
422 				//iRangeFactor = (iRangeChange * (morale - 1)) / 4;
423 				iRangeFactor = (iRangeChange * iRangeFactorMultiplier) / 2;
424 				SLOGD("CalcCoverValue: iRangeChange %d, iRangeFactor %d\n",
425 					iRangeChange, iRangeFactor);
426 
427 				// aggression booster for stupider enemies
428 				iMyPosValue += 100 * iRangeFactor * ( 5 - SoldierDifficultyLevel( pMe ) ) / 5 ;
429 
430 				// if factor is positive increase positional value, else decrease it
431 				// change both values, since one or the other could be 0
432 				if (iRangeFactor > 0)
433 				{
434 					iMyPosValue = (iMyPosValue * (100 + iRangeFactor)) / 100;
435 					iHisPosValue = (100 * iHisPosValue) / (100 + iRangeFactor);
436 				}
437 				else if (iRangeFactor < 0)
438 				{
439 					iMyPosValue = (100 * iMyPosValue) / (100 - iRangeFactor);
440 					iHisPosValue = (iHisPosValue * (100 - iRangeFactor)) / 100;
441 				}
442 			}
443 		}
444 	}
445 
446 	// the farther apart we are, the less important the cover differences are
447 	// the less certain his position, the less important cover differences are
448 	iReductionFactor = ((MAX_THREAT_RANGE - iRange) * Threat[uiThreatIndex].iCertainty) /
449 				MAX_THREAT_RANGE;
450 
451 	// divide by a 100 to make the numbers more managable and avoid 32-bit limit
452 	iThisScale = MAX( iMyPosValue, iHisPosValue) / 100;
453 	iThisScale = (iThisScale * iReductionFactor) / 100;
454 	*iTotalScale += iThisScale;
455 	// this helps to decide the percent improvement later
456 
457 	// POSITIVE COVER VALUE INDICATES THE COVER BENEFITS ME, NEGATIVE RESULT
458 	// MEANS IT BENEFITS THE OTHER GUY.
459 	// divide by a 100 to make the numbers more managable and avoid 32-bit limit
460 	iCoverValue = (iMyPosValue - iHisPosValue) / 100;
461 	iCoverValue = (iCoverValue * iReductionFactor) / 100;
462 
463 	SLOGD("CalcCoverValue: iCoverValue %d, sMyGridNo %d, sHisGrid %d, iRange %d, morale %d",
464 		iCoverValue, sMyGridNo, sHisGridNo, iRange, morale);
465 	SLOGD("CalcCoverValue: iCertainty %d, his bOppCnt %d, my bOppCnt %d",
466 		Threat[uiThreatIndex].iCertainty, pHim->bOppCnt, pMe->bOppCnt);
467 	SLOGD("CalcCoverValue: bHisCTGT = %d, hisThreat = %d, hisFullAPs = %d",
468 		bHisCTGT, Threat[uiThreatIndex].iValue, Threat[uiThreatIndex].iAPs);
469 	SLOGD("CalcCoverValue: bMyCTGT = %d,  iMyThreat = %d,  iMyAPsLeft = %d",
470 		bMyCTGT, iMyThreat, iMyAPsLeft);
471 	SLOGD("CalcCoverValue: hisPosValue = %d, myPosValue = %d",
472 		iHisPosValue, iMyPosValue);
473 	SLOGD("CalcCoverValue: iThisScale = %d, iTotalScale = %d, iReductionFactor %d",
474 		iThisScale, *iTotalScale, iReductionFactor);
475 
476 	return( iCoverValue );
477 }
478 
479 
NumberOfTeamMatesAdjacent(SOLDIERTYPE * pSoldier,INT16 sGridNo)480 static UINT8 NumberOfTeamMatesAdjacent(SOLDIERTYPE* pSoldier, INT16 sGridNo)
481 {
482 	INT16 sTempGridNo;
483 
484 	UINT8 ubCount = 0;
485 
486 	for (UINT8 ubLoop = 0; ubLoop < NUM_WORLD_DIRECTIONS; ++ubLoop)
487 	{
488 		sTempGridNo = NewGridNo( sGridNo, DirectionInc( ubLoop ) );
489 		if ( sTempGridNo != sGridNo )
490 		{
491 			const SOLDIERTYPE* const tgt = WhoIsThere2(sGridNo, pSoldier->bLevel);
492 			if (tgt != NULL && tgt != pSoldier && tgt->bTeam == pSoldier->bTeam)
493 			{
494 				ubCount++;
495 			}
496 		}
497 	}
498 
499 	return( ubCount );
500 }
501 
FindBestNearbyCover(SOLDIERTYPE * pSoldier,INT32 morale,INT32 * piPercentBetter)502 INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter)
503 {
504 	// all 32-bit integers for max. speed
505 	INT32 iCurrentCoverValue, iCoverValue, iBestCoverValue;
506 	INT32 iCurrentScale, iCoverScale;
507 	INT32 iDistFromOrigin, iDistCoverFromOrigin, iThreatCertainty;
508 	INT16 sGridNo, sBestCover = NOWHERE;
509 	INT32 iPathCost;
510 	INT32 iThreatRange, iClosestThreatRange = 1500;
511 //	INT16 sClosestThreatGridno = NOWHERE;
512 	INT32 iMyThreatValue;
513 	INT16 sThreatLoc;
514 	INT32 iMaxThreatRange;
515 	UINT32 uiThreatCnt = 0;
516 	INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange;
517 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
518 	INT16 sOrigin;	// has to be a short, need a pointer
519 	INT16 *pusLastLoc;
520 	INT8 *pbPersOL;
521 	INT8 *pbPublOL;
522 
523 	UINT8 ubBackgroundLightLevel;
524 	UINT8 ubBackgroundLightPercent = 0;
525 	UINT8 ubLightPercentDifference;
526 	BOOLEAN fNight;
527 
528 	INT32 iBestCoverScale = 0; // XXX HACK000E
529 
530 	bool const fHasGasMask = IsWearingHeadGear(*pSoldier, GASMASK);
531 
532 	if ( gbWorldSectorZ > 0 )
533 	{
534 		fNight = FALSE;
535 	}
536 	else
537 	{
538 		ubBackgroundLightLevel = GetTimeOfDayAmbientLightLevel();
539 
540 		if ( ubBackgroundLightLevel < NORMAL_LIGHTLEVEL_DAY + 2 )
541 		{
542 			fNight = FALSE;
543 		}
544 		else
545 		{
546 			fNight = TRUE;
547 			ubBackgroundLightPercent = gbLightSighting[ 0 ][ ubBackgroundLightLevel ];
548 		}
549 	}
550 
551 
552 	iBestCoverValue = -1;
553 
554 #if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
555 	if (gfDisplayCoverValues)
556 	{
557 		std::fill_n(gsCoverValue, WORLD_MAX, 0x7F7F);
558 	}
559 #endif
560 
561 	// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists
562 
563 	pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
564 
565 	// hang a pointer into personal opplist
566 	pbPersOL = &(pSoldier->bOppList[0]);
567 	// hang a pointer into public opplist
568 	pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
569 
570 	// decide how far we're gonna be looking
571 	iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
572 
573 	/*
574 	switch (pSoldier->bAttitude)
575 	{
576 		case DEFENSIVE:		iSearchRange += 2; break;
577 		case BRAVESOLO:		iSearchRange -= 4; break;
578 		case BRAVEAID:		iSearchRange -= 4; break;
579 		case CUNNINGSOLO:	iSearchRange += 4; break;
580 		case CUNNINGAID:	iSearchRange += 4; break;
581 		case AGGRESSIVE:	iSearchRange -= 2; break;
582 	}*/
583 
584 
585 	// maximum search range is 1 tile / 8 pts of wisdom
586 	if (iSearchRange > (pSoldier->bWisdom / 8))
587 	{
588 		iSearchRange = (pSoldier->bWisdom / 8);
589 	}
590 
591 	if (!gfTurnBasedAI)
592 	{
593 		// don't search so far in realtime
594 		iSearchRange /= 2;
595 	}
596 
597 	if (pSoldier->bAlertStatus >= STATUS_RED)          // if already in battle
598 	{
599 		UINT16 const usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_TAKE_COVER);
600 
601 		// must be able to reach the cover, so it can't possibly be more than
602 		// action points left (rounded down) tiles away, since minimum
603 		// cost to move per tile is 1 points.
604 		iMaxMoveTilesLeft = __max( 0, pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) );
605 
606 		// if we can't go as far as the usual full search range
607 		if (iMaxMoveTilesLeft < iSearchRange)
608 		{
609 			// then limit the search range to only as far as we CAN go
610 			iSearchRange = iMaxMoveTilesLeft;
611 		}
612 	}
613 
614 	if (iSearchRange <= 0)
615 	{
616 		return(NOWHERE);
617 	}
618 
619 	// those within 20 tiles of any tile we'll CONSIDER as cover are important
620 	iMaxThreatRange = MAX_THREAT_RANGE + (CELL_X_SIZE * iSearchRange);
621 
622 	// calculate OUR OWN general threat value (not from any specific location)
623 	iMyThreatValue = CalcManThreatValue(pSoldier,NOWHERE,FALSE,pSoldier);
624 
625 	// look through all opponents for those we know of
626 	FOR_EACH_MERC(i)
627 	{
628 		SOLDIERTYPE* const pOpponent = *i;
629 
630 		// if this merc is inactive, at base, on assignment, dead, unconscious
631 		if (pOpponent->bLife < OKLIFE) continue; // next merc
632 
633 		// if this man is neutral / on the same side, he's not an opponent
634 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
635 		{
636 			continue;          // next merc
637 		}
638 
639 		pbPersOL = pSoldier->bOppList + pOpponent->ubID;
640 		pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID;
641 		pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID;
642 
643 		// if this opponent is unknown personally and publicly
644 		if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
645 		{
646 			continue;          // next merc
647 		}
648 
649 		// Special stuff for Carmen the bounty hunter
650 		if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
651 		{
652 			continue;  // next opponent
653 		}
654 
655 		// if personal knowledge is more up to date or at least equal
656 		if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
657 			(*pbPersOL == *pbPublOL))
658 		{
659 			// using personal knowledge, obtain opponent's "best guess" gridno
660 			sThreatLoc = *pusLastLoc;
661 			iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE];
662 		}
663 		else
664 		{
665 			// using public knowledge, obtain opponent's "best guess" gridno
666 			sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
667 			iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
668 		}
669 
670 		// calculate how far away this threat is (in adjusted pixels)
671 		//iThreatRange = AdjPixelsAway(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sThreatLoc),CenterY(sThreatLoc));
672 		iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc );
673 
674 		// if this opponent is believed to be too far away to really be a threat
675 		if (iThreatRange > iMaxThreatRange)
676 		{
677 			continue;          // check next opponent
678 		}
679 
680 		// remember this opponent as a current threat, but DON'T REDUCE FOR COVER!
681 		Threat[uiThreatCnt].iValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
682 
683 		// if the opponent is no threat at all for some reason
684 		if (Threat[uiThreatCnt].iValue == -999)
685 		{
686 			continue;          // check next opponent
687 		}
688 
689 		Threat[uiThreatCnt].pOpponent		= pOpponent;
690 		Threat[uiThreatCnt].sGridNo			= sThreatLoc;
691 		Threat[uiThreatCnt].iCertainty	= iThreatCertainty;
692 		Threat[uiThreatCnt].iOrigRange	= iThreatRange;
693 
694 		// calculate how many APs he will have at the start of the next turn
695 		Threat[uiThreatCnt].iAPs = CalcActionPoints(pOpponent);
696 
697 		if (iThreatRange < iClosestThreatRange)
698 		{
699 			iClosestThreatRange = iThreatRange;
700 //			sClosestThreatGridNo = sThreatLoc;
701 		}
702 
703 		uiThreatCnt++;
704 	}
705 
706 	// if no known opponents were found to threaten us, can't worry about cover
707 	if (!uiThreatCnt)
708 	{
709 		return(sBestCover);
710 	}
711 
712 	// calculate our current cover value in the place we are now, since the
713 	// cover we are searching for must be better than what we have now!
714 	iCurrentCoverValue = 0;
715 	iCurrentScale = 0;
716 
717 	// for every opponent that threatens, consider this spot's cover vs. him
718 	for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; ++uiLoop)
719 	{
720 		// if this threat is CURRENTLY within 20 tiles
721 		if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE)
722 		{
723 			// add this opponent's cover value to our current total cover value
724 			iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale);
725 		}
726 	}
727 
728 	iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
729 
730 	// determine maximum horizontal limits
731 	sMaxLeft  = MIN(iSearchRange,(pSoldier->sGridNo % MAXCOL));
732 	sMaxRight = MIN(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
733 
734 	// determine maximum vertical limits
735 	sMaxUp   = MIN(iSearchRange,(pSoldier->sGridNo / MAXROW));
736 	sMaxDown = MIN(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
737 
738 	iRoamRange = RoamingRange(pSoldier,&sOrigin);
739 
740 	// if status isn't black (life & death combat), and roaming range is limited
741 	if ((pSoldier->bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) &&
742 		(sOrigin != NOWHERE))
743 	{
744 		// must try to stay within or return to the point of origin
745 		iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo);
746 	}
747 	else
748 	{
749 		// don't care how far from origin we go
750 		iDistFromOrigin = -1;
751 	}
752 	SLOGD("FBNC: iRoamRange %d, sMaxLeft %d, sMaxRight %d, sMaxUp %d, sMaxDown %d",
753 		iRoamRange, sMaxLeft, sMaxRight, sMaxUp, sMaxDown);
754 
755 	// the initial cover value to beat is our current cover value
756 	iBestCoverValue = iCurrentCoverValue;
757 	SLOGD("FBNC: CURRENT iCoverValue = %d\n",iCurrentCoverValue);
758 
759 	if (pSoldier->bAlertStatus >= STATUS_RED)          // if already in battle
760 	{
761 		// to speed this up, tell PathAI to cancel any paths beyond our AP reach!
762 		gubNPCAPBudget = pSoldier->bActionPoints;
763 	}
764 	else
765 	{
766 		// even if not under pressure, limit to 1 turn's travelling distance
767 		// hope this isn't too expensive...
768 		gubNPCAPBudget = CalcActionPoints( pSoldier );
769 		//gubNPCAPBudget = pSoldier->bInitialAPs;
770 	}
771 
772 	// Call FindBestPath to set flags in all locations that we can
773 	// walk into within range.  We have to set some things up first...
774 
775 	// set the distance limit of the square region
776 	gubNPCDistLimit = (UINT8) iSearchRange;
777 
778 	// reset the "reachable" flags in the region we're looking at
779 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
780 	{
781 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
782 		{
783 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
784 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
785 			{
786 				continue;
787 			}
788 			gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
789 		}
790 	}
791 
792 	FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 0 );
793 
794 	// Turn off the "reachable" flag for his current location
795 	// so we don't consider it
796 	gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
797 
798 	// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
799 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
800 	{
801 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
802 		{
803 			//HandleMyMouseCursor(KEYBOARDALSO);
804 
805 			// calculate the next potential gridno
806 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
807 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
808 			{
809 				continue;
810 			}
811 
812 			// if we are limited to staying/returning near our place of origin
813 			if (iDistFromOrigin != -1)
814 			{
815 				iDistCoverFromOrigin = SpacesAway(sOrigin,sGridNo);
816 
817 				// if this is outside roaming range, and doesn't get us closer to it
818 				if ((iDistCoverFromOrigin > iRoamRange) &&
819 					(iDistFromOrigin <= iDistCoverFromOrigin))
820 				{
821 					continue;	// then we can't go there
822 				}
823 			}
824 
825 			if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
826 			{
827 				continue;
828 			}
829 
830 			if ( !fHasGasMask )
831 			{
832 				if ( InGas( pSoldier, sGridNo ) )
833 				{
834 					continue;
835 				}
836 			}
837 
838 			// ignore blacklisted spot
839 			if ( sGridNo == pSoldier->sBlackList )
840 			{
841 				continue;
842 			}
843 
844 			iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset];
845 			/*
846 			// water is OK, if the only good hiding place requires us to get wet, OK
847 			iPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,WATEROK);
848 
849 			if (!iPathCost)
850 			{
851 				continue;        // skip on to the next potential grid
852 			}
853 
854 			// CJC: This should be a redundent check because the path code is given an
855 			// AP limit to begin with!
856 			if (pSoldier->bAlertStatus == STATUS_BLACK)      // in battle
857 			{
858 				// must be able to afford the APs to get to this cover this turn
859 				if (iPathCost > pSoldier->bActionPoints)
860 				{
861 					continue;      // skip on to the next potential grid
862 				}
863 			}
864 			*/
865 
866 			// OK, this place shows potential.  How useful is it as cover?
867 			// EVALUATE EACH GRID #, remembering the BEST PROTECTED ONE
868 			iCoverValue = 0;
869 			iCoverScale = 0;
870 
871 			// for every opponent that threatens, consider this spot's cover vs. him
872 			for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; ++uiLoop)
873 			{
874 				// calculate the range we would be at from this opponent
875 				iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, Threat[uiLoop].sGridNo );
876 				// if this threat would be within 20 tiles, count it
877 				if (iThreatRange <= MAX_THREAT_RANGE)
878 				{
879 					iCoverValue += CalcCoverValue(pSoldier,sGridNo,iMyThreatValue,
880 						(pSoldier->bActionPoints - iPathCost),
881 						uiLoop,iThreatRange,morale,&iCoverScale);
882 				}
883 			}
884 
885 			// reduce cover for each person adjacent to this gridno who is on our team,
886 			// by 10% (so locations next to several people will be very much frowned upon
887 			if ( iCoverValue >= 0 )
888 			{
889 				iCoverValue -= (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
890 			}
891 			else
892 			{
893 				// when negative, must add a negative to decrease the total
894 				iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
895 			}
896 
897 			if (fNight && GetRoom(sGridNo) == NO_ROOM) // ignore in buildings in case placed there
898 			{
899 				// reduce cover at nighttime based on how bright the light is at that location
900 				// using the difference in sighting distance between the background and the
901 				// light for this tile
902 				ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->bLevel ) ] - ubBackgroundLightPercent );
903 				if ( iCoverValue >= 0 )
904 				{
905 					iCoverValue -= (iCoverValue / 100) * ubLightPercentDifference;
906 				}
907 				else
908 				{
909 					iCoverValue += (iCoverValue / 100) * ubLightPercentDifference;
910 				}
911 			}
912 
913 			// if there ARE multiple opponents
914 			if (uiThreatCnt > 1)
915 			{
916 				SLOGD("FBNC: Total iCoverValue at gridno %d is %d",
917 					sGridNo, iCoverValue);
918 			}
919 
920 #if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
921 			if (gfDisplayCoverValues)
922 			{
923 				gsCoverValue[sGridNo] = (INT16) (iCoverValue / 100);
924 			}
925 #endif
926 
927 			// if this is better than the best place found so far
928 
929 			if (iCoverValue > iBestCoverValue)
930 			{
931 				SLOGD("FBNC: NEW BEST iCoverValue at gridno %d is %d",
932 					sGridNo, iCoverValue);
933 				// remember it instead
934 				sBestCover = sGridNo;
935 				iBestCoverValue = iCoverValue;
936 				iBestCoverScale = iCoverScale;
937 			}
938 		}
939 	}
940 
941 	gubNPCAPBudget = 0;
942 	gubNPCDistLimit = 0;
943 
944 	#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
945 	if (gfDisplayCoverValues)
946 	{
947 		// do a locate?
948 		LocateSoldier(pSoldier, SETLOCATORFAST);
949 		gsBestCover = sBestCover;
950 		SetRenderFlags( RENDER_FLAG_FULL );
951 		RenderWorld();
952 		RenderCoverDebug( );
953 		InvalidateScreen( );
954 		RefreshScreen();
955 		/*
956 		iLoop = GetJA2Clock();
957 		do
958 		{
959 
960 		} while( ( GetJA2Clock( ) - iLoop ) < 2000 );
961 		*/
962 	}
963 	#endif
964 
965 	// if a better cover location was found
966 	if (sBestCover != NOWHERE)
967 	{
968 		#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
969 		gsBestCover = sBestCover;
970 		#endif
971 		// cover values already take the AP cost of getting there into account in
972 		// a BIG way, so no need to worry about that here, even small improvements
973 		// are actually very significant once we get our APs back (if we live!)
974 		*piPercentBetter = CalcPercentBetter(iCurrentCoverValue,iBestCoverValue,iCurrentScale,iBestCoverScale);
975 
976 		// if best cover value found was at least 5% better than our current cover
977 		if (*piPercentBetter >= MIN_PERCENT_BETTER)
978 		{
979 			SLOGD(ST::format("Found Cover: current {}, best {}, %Better {}",
980 				iCurrentCoverValue, iBestCoverValue, *piPercentBetter));
981 			return((INT16)sBestCover);       // return the gridno of that cover
982 		}
983 	}
984 	return(NOWHERE);       // return that no suitable cover was found
985 }
986 
FindSpotMaxDistFromOpponents(SOLDIERTYPE * pSoldier)987 INT16 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier)
988 {
989 	INT16 sGridNo;
990 	INT16 sBestSpot = NOWHERE;
991 	INT32 iThreatRange,iClosestThreatRange = 1500, iSpotClosestThreatRange;
992 	INT16 sThreatLoc, sThreatGridNo[MAXMERCS];
993 	UINT32 uiThreatCnt = 0;
994 	INT32 iSearchRange;
995 	INT16	sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
996 	INT8 * pbPersOL,*pbPublOL, bEscapeDirection, bBestEscapeDirection = -1;
997 	INT16	sOrigin;
998 	INT32	iRoamRange;
999 
1000 	bool const fHasGasMask = IsWearingHeadGear(*pSoldier, GASMASK);
1001 
1002 	// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplistS
1003 
1004 	// look through all opponents for those we know of
1005 	FOR_EACH_MERC(i)
1006 	{
1007 		SOLDIERTYPE* const pOpponent = *i;
1008 
1009 		// if this merc is inactive, at base, on assignment, dead, unconscious
1010 		if (pOpponent->bLife < OKLIFE) continue; // next merc
1011 
1012 		// if this man is neutral / on the same side, he's not an opponent
1013 		if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
1014 		{
1015 			continue;          // next merc
1016 		}
1017 
1018 		pbPersOL = &(pSoldier->bOppList[pOpponent->ubID]);
1019 		pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]);
1020 
1021 		// if this opponent is unknown personally and publicly
1022 		if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
1023 		{
1024 			continue;          // check next opponent
1025 		}
1026 
1027 		// Special stuff for Carmen the bounty hunter
1028 		if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
1029 		{
1030 			continue;  // next opponent
1031 		}
1032 
1033 		// if the opponent is no threat at all for some reason
1034 		if (CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier) == -999)
1035 		{
1036 			continue;          // check next opponent
1037 		}
1038 
1039 		// if personal knowledge is more up to date or at least equal
1040 		if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
1041 			(*pbPersOL == *pbPublOL))
1042 		{
1043 			// using personal knowledge, obtain opponent's "best guess" gridno
1044 			sThreatLoc = gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID];
1045 		}
1046 		else
1047 		{
1048 			// using public knowledge, obtain opponent's "best guess" gridno
1049 			sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
1050 		}
1051 
1052 		// calculate how far away this threat is (in adjusted pixels)
1053 		iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc );
1054 
1055 		if (iThreatRange < iClosestThreatRange)
1056 		{
1057 			iClosestThreatRange = iThreatRange;
1058 		}
1059 
1060 		// remember this threat's gridno
1061 		sThreatGridNo[uiThreatCnt] = sThreatLoc;
1062 		uiThreatCnt++;
1063 	}
1064 
1065 	// if no known opponents were found to threaten us, can't worry about them
1066 	if (!uiThreatCnt)
1067 	{
1068 		return( sBestSpot );
1069 	}
1070 
1071 	// get roaming range here; for civilians, running away is limited by roam range
1072 	if ( pSoldier->bTeam == CIV_TEAM )
1073 	{
1074 		iRoamRange = RoamingRange( pSoldier, &sOrigin );
1075 		if ( iRoamRange == 0 )
1076 		{
1077 			return( sBestSpot );
1078 		}
1079 	}
1080 	else
1081 	{
1082 		// dummy values
1083 		iRoamRange = 100;
1084 		sOrigin = pSoldier->sGridNo;
1085 	}
1086 
1087 	// DETERMINE CO-ORDINATE LIMITS OF SQUARE AREA TO BE CHECKED
1088 	// THIS IS A LOT QUICKER THAN COVER, SO DO A LARGER AREA, NOT AFFECTED BY
1089 	// DIFFICULTY SETTINGS...
1090 
1091 	if (pSoldier->bAlertStatus == STATUS_BLACK)          // if already in battle
1092 	{
1093 		iSearchRange = pSoldier->bActionPoints / 2;
1094 
1095 		// to speed this up, tell PathAI to cancel any paths beyond our AP reach!
1096 		gubNPCAPBudget = pSoldier->bActionPoints;
1097 	}
1098 	else
1099 	{
1100 		// even if not under pressure, limit to 1 turn's travelling distance
1101 		gubNPCAPBudget = __min( pSoldier->bActionPoints / 2, CalcActionPoints( pSoldier ) );
1102 
1103 		iSearchRange = gubNPCAPBudget / 2;
1104 	}
1105 
1106 	if (!gfTurnBasedAI)
1107 	{
1108 		// search only half as far in realtime
1109 		// but always allow a certain minimum!
1110 
1111 		if ( iSearchRange > 4 )
1112 		{
1113 			iSearchRange /= 2;
1114 			gubNPCAPBudget /= 2;
1115 		}
1116 	}
1117 
1118 
1119 	// assume we have to stand up!
1120 	// use the min macro here to make sure we don't wrap the UINT8 to 255...
1121 
1122 	#if 0 /* doppelt? */
1123 	gubNPCAPBudget = gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) );
1124 	#else
1125 	gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) );
1126 	#endif
1127 
1128 	// determine maximum horizontal limits
1129 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1130 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1131 
1132 	// determine maximum vertical limits
1133 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1134 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1135 
1136 	// Call FindBestPath to set flags in all locations that we can
1137 	// walk into within range.  We have to set some things up first...
1138 
1139 	// set the distance limit of the square region
1140 	gubNPCDistLimit = (UINT8) iSearchRange;
1141 
1142 	// reset the "reachable" flags in the region we're looking at
1143 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1144 	{
1145 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1146 		{
1147 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1148 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1149 			{
1150 				continue;
1151 			}
1152 
1153 			gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1154 		}
1155 	}
1156 
1157 	FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, DetermineMovementMode( pSoldier, AI_ACTION_RUN_AWAY ), COPYREACHABLE, 0 );
1158 
1159 	// Turn off the "reachable" flag for his current location
1160 	// so we don't consider it
1161 	gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1162 
1163 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1164 	{
1165 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1166 		{
1167 			// calculate the next potential gridno
1168 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1169 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1170 			{
1171 				continue;
1172 			}
1173 
1174 			if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
1175 			{
1176 				continue;
1177 			}
1178 
1179 			if ( sGridNo == pSoldier->sBlackList )
1180 			{
1181 				continue;
1182 			}
1183 
1184 			if ( !fHasGasMask )
1185 			{
1186 				if ( InGas( pSoldier, sGridNo ) )
1187 				{
1188 					continue;
1189 				}
1190 			}
1191 
1192 			if ( pSoldier->bTeam == CIV_TEAM )
1193 			{
1194 				iRoamRange = RoamingRange( pSoldier, &sOrigin );
1195 				if ( PythSpacesAway( sOrigin, sGridNo ) > iRoamRange )
1196 				{
1197 					continue;
1198 				}
1199 			}
1200 
1201 			// exclude locations with tear/mustard gas (at this point, smoke is cool!)
1202 			if ( InGas( pSoldier, sGridNo ) )
1203 			{
1204 				continue;
1205 			}
1206 
1207 			// OK, this place shows potential.  How useful is it as cover?
1208 
1209 			iSpotClosestThreatRange = 1500;
1210 
1211 			if ( pSoldier->bTeam == ENEMY_TEAM && GridNoOnEdgeOfMap( sGridNo, &bEscapeDirection ) )
1212 			{
1213 				// We can escape!  This is better than anything else except a closer spot which we can
1214 				// cross over from.
1215 
1216 				// Subtract the straight-line distance from our location to this one as an estimate of
1217 				// path cost and for looks...
1218 
1219 				// The edge spot closest to us which is on the edge will have the highest value, so
1220 				// it will be picked over locations further away.
1221 				// Only reachable gridnos will be picked so this should hopefully look okay
1222 				iSpotClosestThreatRange -= PythSpacesAway( pSoldier->sGridNo, sGridNo );
1223 
1224 			}
1225 			else
1226 			{
1227 				bEscapeDirection = -1;
1228 				// for every opponent that threatens, consider this spot's cover vs. him
1229 				for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; ++uiLoop)
1230 				{
1231 					//iThreatRange = AdjPixelsAway(CenterX(sGridNo),CenterY(sGridNo), CenterX(sThreatGridNo[iLoop]),CenterY(sThreatGridNo[iLoop]));
1232 					iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, sThreatGridNo[uiLoop] );
1233 					if (iThreatRange < iSpotClosestThreatRange)
1234 					{
1235 						iSpotClosestThreatRange = iThreatRange;
1236 					}
1237 				}
1238 			}
1239 
1240 			// if this is better than the best place found so far
1241 			// (i.e. the closest guy would be farther away than previously)
1242 			if (iSpotClosestThreatRange > iClosestThreatRange)
1243 			{
1244 				// remember it instead
1245 				iClosestThreatRange = iSpotClosestThreatRange;
1246 				sBestSpot = sGridNo;
1247 				bBestEscapeDirection = bEscapeDirection;
1248 			}
1249 		}
1250 
1251 	}
1252 
1253 	gubNPCAPBudget = 0;
1254 	gubNPCDistLimit = 0;
1255 
1256 	if (bBestEscapeDirection != -1)
1257 	{
1258 		// Woohoo!  We can escape!  Fake some stuff with the quote-related actions
1259 		pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bBestEscapeDirection );
1260 	}
1261 
1262 	return( sBestSpot );
1263 }
1264 
FindNearestUngassedLand(SOLDIERTYPE * pSoldier)1265 INT16 FindNearestUngassedLand(SOLDIERTYPE *pSoldier)
1266 {
1267 	INT16 sGridNo,sClosestLand = NOWHERE,sPathCost,sShortestPath = 1000;
1268 	INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset;
1269 	INT32 iSearchRange;
1270 
1271 	// start with a small search area, and expand it if we're unsuccessful
1272 	// this should almost never need to search farther than 5 or 10 squares...
1273 	for (iSearchRange = 5; iSearchRange <= 25; iSearchRange += 5)
1274 	{
1275 		// determine maximum horizontal limits
1276 		sMaxLeft  = MIN(iSearchRange,(pSoldier->sGridNo % MAXCOL));
1277 		sMaxRight = MIN(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1278 
1279 		// determine maximum vertical limits
1280 		sMaxUp   = MIN(iSearchRange,(pSoldier->sGridNo / MAXROW));
1281 		sMaxDown = MIN(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1282 
1283 		// Call FindBestPath to set flags in all locations that we can
1284 		// walk into within range.  We have to set some things up first...
1285 
1286 		// set the distance limit of the square region
1287 		gubNPCDistLimit = (UINT8) iSearchRange;
1288 
1289 		// reset the "reachable" flags in the region we're looking at
1290 		for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1291 		{
1292 			for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1293 			{
1294 				sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1295 				if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1296 				{
1297 					continue;
1298 				}
1299 
1300 				gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1301 			}
1302 		}
1303 
1304 		FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 0 );
1305 
1306 		// Turn off the "reachable" flag for his current location
1307 		// so we don't consider it
1308 		gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1309 
1310 		// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
1311 		for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1312 		{
1313 			for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1314 			{
1315 				// calculate the next potential gridno
1316 				sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1317 				if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1318 				{
1319 					continue;
1320 				}
1321 
1322 				if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
1323 				{
1324 					continue;
1325 				}
1326 
1327 				// ignore blacklisted spot
1328 				if ( sGridNo == pSoldier->sBlackList )
1329 				{
1330 					continue;
1331 				}
1332 
1333 				// CJC: here, unfortunately, we must calculate a path so we have an AP cost
1334 
1335 				// obviously, we're looking for LAND, so water is out!
1336 				sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
1337 
1338 				if (!sPathCost)
1339 				{
1340 					continue;      // skip on to the next potential grid
1341 				}
1342 
1343 				// if this path is shorter than the one to the closest land found so far
1344 				if (sPathCost < sShortestPath)
1345 				{
1346 					// remember it instead
1347 					sShortestPath = sPathCost;
1348 					sClosestLand = sGridNo;
1349 				}
1350 			}
1351 		}
1352 
1353 		// if we found a piece of land in this search area
1354 		if (sClosestLand != NOWHERE)  // quit now, no need to look any farther
1355 			break;
1356 	}
1357 	return(sClosestLand);
1358 }
1359 
FindNearbyDarkerSpot(SOLDIERTYPE * pSoldier)1360 INT16 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier )
1361 {
1362 	INT16 sGridNo, sClosestSpot = NOWHERE, sPathCost;
1363 	INT32 iSpotValue, iBestSpotValue = 1000;
1364 	INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset;
1365 	INT32 iSearchRange;
1366 	INT8 bLightLevel, bCurrLightLevel, bLightDiff;
1367 	INT32 iRoamRange;
1368 	INT16 sOrigin;
1369 
1370 	bCurrLightLevel = LightTrueLevel( pSoldier->sGridNo, pSoldier->bLevel );
1371 
1372 	iRoamRange = RoamingRange( pSoldier, &sOrigin );
1373 
1374 	// start with a small search area, and expand it if we're unsuccessful
1375 	// this should almost never need to search farther than 5 or 10 squares...
1376 	for (iSearchRange = 5; iSearchRange <= 15; iSearchRange += 5)
1377 	{
1378 		// determine maximum horizontal limits
1379 		sMaxLeft  = MIN(iSearchRange,(pSoldier->sGridNo % MAXCOL));
1380 		sMaxRight = MIN(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1381 
1382 		// determine maximum vertical limits
1383 		sMaxUp   = MIN(iSearchRange,(pSoldier->sGridNo / MAXROW));
1384 		sMaxDown = MIN(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1385 
1386 		// Call FindBestPath to set flags in all locations that we can
1387 		// walk into within range.  We have to set some things up first...
1388 
1389 		// set the distance limit of the square region
1390 		gubNPCDistLimit = (UINT8) iSearchRange;
1391 
1392 		// reset the "reachable" flags in the region we're looking at
1393 		for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1394 		{
1395 			for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1396 			{
1397 				sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1398 				if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1399 				{
1400 					continue;
1401 				}
1402 
1403 				gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1404 			}
1405 		}
1406 
1407 		FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 0 );
1408 
1409 		// Turn off the "reachable" flag for his current location
1410 		// so we don't consider it
1411 		gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1412 
1413 		// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
1414 		for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1415 		{
1416 			for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1417 			{
1418 				// calculate the next potential gridno
1419 				sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1420 				if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1421 				{
1422 					continue;
1423 				}
1424 
1425 				if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
1426 				{
1427 					continue;
1428 				}
1429 
1430 				// ignore blacklisted spot
1431 				if ( sGridNo == pSoldier->sBlackList )
1432 				{
1433 					continue;
1434 				}
1435 
1436 				// require this character to stay within their roam range
1437 				if ( PythSpacesAway( sOrigin, sGridNo ) > iRoamRange )
1438 				{
1439 					continue;
1440 				}
1441 
1442 				// screen out anything brighter than our current best spot
1443 				bLightLevel = LightTrueLevel( sGridNo, pSoldier->bLevel );
1444 
1445 				bLightDiff = gbLightSighting[0][ bCurrLightLevel ] - gbLightSighting[0][ bLightLevel ];
1446 
1447 				// if the spot is darker than our current location, then bLightDiff > 0
1448 				// plus ignore differences of just 1 light level
1449 				if ( bLightDiff <= 1 )
1450 				{
1451 					continue;
1452 				}
1453 
1454 				// CJC: here, unfortunately, we must calculate a path so we have an AP cost
1455 
1456 				sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
1457 
1458 				if (!sPathCost)
1459 				{
1460 					continue;      // skip on to the next potential grid
1461 				}
1462 
1463 				// decrease the "cost" of the spot by the amount of light/darkness
1464 				iSpotValue = sPathCost * 2 - bLightDiff;
1465 
1466 				if ( iSpotValue < iBestSpotValue )
1467 				{
1468 					// remember it instead
1469 					iBestSpotValue = iSpotValue;
1470 					sClosestSpot = sGridNo;
1471 				}
1472 			}
1473 		}
1474 
1475 		// if we found a piece of land in this search area
1476 		if (sClosestSpot != NOWHERE)  // quit now, no need to look any farther
1477 		{
1478 			break;
1479 		}
1480 	}
1481 
1482 	return(sClosestSpot);
1483 }
1484 
1485 
IsGunUsable(OBJECTTYPE const & o,SOLDIERTYPE const & s)1486 static bool IsGunUsable(OBJECTTYPE const& o, SOLDIERTYPE const& s)
1487 {
1488 	Assert(GCM->getItem(o.usItem)->isGun());
1489 	return
1490 		o.bGunAmmoStatus >  0 &&
1491 		o.ubGunShotsLeft != 0 &&
1492 		(!HasObjectImprint(o) || o.ubImprintID == s.ubID); // XXX wrong: should compare profile
1493 }
1494 
1495 
1496 #define MINIMUM_REQUIRED_STATUS 70
1497 
SearchForItems(SOLDIERTYPE & s,ItemSearchReason const reason,UINT16 const usItem)1498 INT8 SearchForItems(SOLDIERTYPE& s, ItemSearchReason const reason, UINT16 const usItem)
1499 {
1500 	if (s.bActionPoints < AP_PICKUP_ITEM) return AI_ACTION_NONE;
1501 
1502 	if (!IS_MERC_BODY_TYPE(&s)) return AI_ACTION_NONE;
1503 
1504 	INT32 search_range = gbDiff[DIFF_MAX_COVER_RANGE][SoldierDifficultyLevel(&s)];
1505 
1506 	switch (s.bAttitude)
1507 	{
1508 		case DEFENSIVE:   search_range -= 1; break;
1509 		case BRAVESOLO:   search_range += 2; break;
1510 		case BRAVEAID:    search_range += 2; break;
1511 		case CUNNINGSOLO: search_range -= 2; break;
1512 		case CUNNINGAID:  search_range -= 2; break;
1513 		case AGGRESSIVE:  search_range += 1; break;
1514 	}
1515 
1516 	// Maximum search range is 1 tile / 10 pts of wisdom
1517 	if (search_range > s.bWisdom / 10)
1518 	{
1519 		search_range = s.bWisdom / 10;
1520 	}
1521 
1522 	if (!gfTurnBasedAI)
1523 	{ // Don't search so far in realtime
1524 		search_range /= 2;
1525 	}
1526 
1527 	// Don't search so far for items
1528 	search_range /= 2;
1529 
1530 	// determine maximum horizontal limits
1531 	INT16 const max_left  = MIN(search_range, s.sGridNo % MAXCOL);
1532 	INT16 const max_right = MIN(search_range, MAXCOL - (s.sGridNo % MAXCOL + 1));
1533 
1534 	// determine maximum vertical limits
1535 	INT16 const max_up   = MIN(search_range, s.sGridNo / MAXROW);
1536 	INT16 const max_down = MIN(search_range, MAXROW - (s.sGridNo / MAXROW + 1));
1537 
1538 	// Call FindBestPath to set flags in all locations that we can
1539 	// walk into within range.  We have to set some things up first...
1540 
1541 	// set the distance limit of the square region
1542 	gubNPCDistLimit = search_range;
1543 
1544 	// set an AP limit too, to our APs less the cost of picking up an item
1545 	// and less the cost of dropping an item since we might need to do that
1546 	gubNPCAPBudget = s.bActionPoints - AP_PICKUP_ITEM;
1547 
1548 	// reset the "reachable" flags in the region we're looking at
1549 	for (INT16 dy = -max_up; dy <= max_down; ++dy)
1550 	{
1551 		for (INT16 dx = -max_left; dx <= max_right; ++dx)
1552 		{
1553 			GridNo const grid_no = s.sGridNo + dx + MAXCOL * dy;
1554 			if (grid_no < 0 || WORLD_MAX <= grid_no) continue;
1555 			gpWorldLevelData[grid_no].uiFlags &= ~MAPELEMENT_REACHABLE;
1556 		}
1557 	}
1558 
1559 	FindBestPath(&s, NOWHERE, s.bLevel, DetermineMovementMode(&s, AI_ACTION_PICKUP_ITEM), COPYREACHABLE, 0);
1560 
1561 	GridNo best_spot     = NOWHERE;
1562 	INT32  best_value    =  0;
1563 	INT32  item_idx      = -1;
1564 	INT32  best_item_idx = -1;
1565 	for (INT16 dy = -max_up; dy <= max_down; ++dy)
1566 	{
1567 		for (INT16 dx = -max_left; dx <= max_right; ++dx)
1568 		{
1569 			// calculate the next potential gridno
1570 			INT16 const grid_no = s.sGridNo + dx + (MAXCOL * dy);
1571 			if (grid_no < 0 || WORLD_MAX <= grid_no) continue;
1572 
1573 			// exclude locations with tear/mustard gas (at this point, smoke is cool!)
1574 			if (InGasOrSmoke(&s, grid_no)) continue;
1575 
1576 			if (!(gpWorldLevelData[grid_no].uiFlags & MAPELEMENT_ITEMPOOL_PRESENT)) continue;
1577 			if (!(gpWorldLevelData[grid_no].uiFlags & MAPELEMENT_REACHABLE))        continue;
1578 
1579 			// ignore blacklisted spot
1580 			if (grid_no == s.sBlackList) continue;
1581 
1582 			INT32 value = 0;
1583 			for (ITEM_POOL const* pItemPool = GetItemPool(grid_no, s.bLevel); pItemPool; pItemPool = pItemPool->pNext)
1584 			{
1585 				OBJECTTYPE const& o    = GetWorldItem(pItemPool->iItemIndex).o;
1586 				const ItemModel * item = GCM->getItem(o.usItem);
1587 				INT32             temp_value;
1588 				switch (reason)
1589 				{
1590 					case SEARCH_AMMO:
1591 						// We are looking for ammo to match the gun in usItem
1592 						if (item->getItemClass() == IC_GUN && o.bStatus[0] >= MINIMUM_REQUIRED_STATUS)
1593 						{ // Maybe this gun has ammo (adjust for whether it is better than ours!)
1594 							if (!IsGunUsable(o, s)) continue;
1595 							temp_value = o.ubGunShotsLeft * GCM->getWeapon(o.usItem)->ubDeadliness / GCM->getWeapon(usItem)->ubDeadliness;
1596 						}
1597 						else
1598 						{
1599 							if (!ValidAmmoType(usItem, o.usItem)) continue;
1600 							temp_value = TotalPoints(&o);
1601 						}
1602 						break;
1603 
1604 					default:
1605 						if (item->getItemClass() == IC_ARMOUR && o.bStatus[0] >= MINIMUM_REQUIRED_STATUS)
1606 						{
1607 							InvSlotPos pos;
1608 							switch (Armour[item->getClassIndex()].ubArmourClass)
1609 							{
1610 								case ARMOURCLASS_HELMET:   pos = HELMETPOS; break;
1611 								case ARMOURCLASS_VEST:     pos = VESTPOS;   break;
1612 								case ARMOURCLASS_LEGGINGS: pos = LEGPOS;    break;
1613 								default: continue;
1614 							}
1615 							OBJECTTYPE const& cur_armour = s.inv[pos];
1616 							if (cur_armour.usItem == NOTHING)
1617 							{
1618 								temp_value = 200 + EffectiveArmour(&o);
1619 							}
1620 							else
1621 							{
1622 								INT8 const new_rating = EffectiveArmour(&o);
1623 								if (EffectiveArmour(&s.inv[HELMETPOS]) <= new_rating) continue; // XXX makes no sense: always compare with helmet and only consider when worse
1624 								temp_value = 100 * new_rating / EffectiveArmour(&cur_armour);
1625 							}
1626 						}
1627 						/* FALLTHROUGH */
1628 
1629 					case SEARCH_WEAPONS:
1630 					{
1631 						if (!(item->isWeapon()))                 continue;
1632 						if (o.bStatus[0] < MINIMUM_REQUIRED_STATUS)          continue;
1633 						if (item->isGun()&& !IsGunUsable(o, s)) continue;
1634 						const WeaponModel * new_wpn = GCM->getWeapon(o.usItem);
1635 						OBJECTTYPE const& in_hand = s.inv[HANDPOS];
1636 						if (GCM->getItem(in_hand.usItem)->isWeapon())
1637 						{
1638 							const WeaponModel * cur_wpn = GCM->getWeapon(in_hand.usItem);
1639 							if (new_wpn->ubDeadliness <= cur_wpn->ubDeadliness) continue;
1640 							temp_value = 100 * new_wpn->ubDeadliness / cur_wpn->ubDeadliness;
1641 						}
1642 						else
1643 						{
1644 							temp_value = 200 + new_wpn->ubDeadliness;
1645 						}
1646 						break;
1647 					}
1648 				}
1649 
1650 				if (value < temp_value)
1651 				{
1652 					value    = temp_value;
1653 					item_idx = pItemPool->iItemIndex;
1654 				}
1655 			}
1656 			value = 3 * value / (3 + PythSpacesAway(grid_no, s.sGridNo));
1657 			if (best_value < value)
1658 			{
1659 				best_value    = value;
1660 				best_spot     = grid_no;
1661 				best_item_idx = item_idx;
1662 			}
1663 		}
1664 	}
1665 
1666 	if (best_spot == NOWHERE) return AI_ACTION_NONE;
1667 
1668 	OBJECTTYPE const& o = GetWorldItem(best_item_idx).o;
1669 	SLOGD("%d decides to pick up %s", s.ubID, ItemNames[o.usItem].c_str());
1670 	if (GCM->getItem(o.usItem)->getItemClass() == IC_GUN &&
1671 		!FindBetterSpotForItem(s, HANDPOS))
1672 	{
1673 		if (s.bActionPoints < AP_PICKUP_ITEM + AP_PICKUP_ITEM)
1674 		{
1675 			return AI_ACTION_NONE;
1676 		}
1677 		if (s.inv[HANDPOS].fFlags & OBJECT_UNDROPPABLE)
1678 		{ // Destroy this item
1679 			SLOGD("%d decides he must drop %s first so destroys it", s.ubID, ItemNames[s.inv[HANDPOS].usItem].c_str());
1680 			DeleteObj(&s.inv[HANDPOS]);
1681 			DeductPoints(&s, AP_PICKUP_ITEM, 0);
1682 		}
1683 		else
1684 		{ // We want to drop this item
1685 			SLOGD("%d decides he must drop %s first", s.ubID, ItemNames[s.inv[HANDPOS].usItem].c_str());
1686 			s.bNextAction            = AI_ACTION_PICKUP_ITEM;
1687 			s.usNextActionData       = best_spot;
1688 			s.iNextActionSpecialData = best_item_idx;
1689 			return AI_ACTION_DROP_ITEM;
1690 		}
1691 	}
1692 	s.uiPendingActionData1 = best_item_idx;
1693 	s.usActionData         = best_spot;
1694 	return AI_ACTION_PICKUP_ITEM;
1695 }
1696 
1697 
FindClosestDoor(SOLDIERTYPE * pSoldier)1698 INT16 FindClosestDoor( SOLDIERTYPE * pSoldier )
1699 {
1700 	INT16 sClosestDoor = NOWHERE;
1701 	INT32 iSearchRange;
1702 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1703 	INT16 sGridNo;
1704 	INT32 iDist, iClosestDist = 10;
1705 
1706 	iSearchRange = 5;
1707 
1708 	// determine maximum horizontal limits
1709 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1710 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1711 
1712 	// determine maximum vertical limits
1713 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1714 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1715 	// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
1716 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1717 	{
1718 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1719 		{
1720 			// calculate the next potential gridno
1721 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1722 			if (FindStructure( sGridNo, STRUCTURE_ANYDOOR ) != NULL)
1723 			{
1724 				iDist = PythSpacesAway( pSoldier->sGridNo, sGridNo );
1725 				if (iDist < iClosestDist)
1726 				{
1727 					iClosestDist = iDist;
1728 					sClosestDoor = sGridNo;
1729 				}
1730 			}
1731 		}
1732 	}
1733 
1734 	return( sClosestDoor );
1735 }
1736 
FindNearestEdgepointOnSpecifiedEdge(INT16 sGridNo,INT8 bEdgeCode)1737 INT16 FindNearestEdgepointOnSpecifiedEdge( INT16 sGridNo, INT8 bEdgeCode )
1738 {
1739 	std::vector<INT16>* pEdgepoints;
1740 	INT16 sClosestSpot = NOWHERE, sClosestDist = 0x7FFF, sTempDist;
1741 
1742 	switch( bEdgeCode )
1743 	{
1744 		case NORTH_EDGEPOINT_SEARCH:
1745 			pEdgepoints = &gps1stNorthEdgepointArray;
1746 			break;
1747 		case EAST_EDGEPOINT_SEARCH:
1748 			pEdgepoints = &gps1stEastEdgepointArray;
1749 			break;
1750 		case SOUTH_EDGEPOINT_SEARCH:
1751 			pEdgepoints = &gps1stSouthEdgepointArray;
1752 			break;
1753 		case WEST_EDGEPOINT_SEARCH:
1754 			pEdgepoints = &gps1stWestEdgepointArray;
1755 			break;
1756 		default:
1757 			// WTF???
1758 			return( NOWHERE );
1759 	}
1760 
1761 	// Do a 2D search to find the closest map edgepoint and
1762 	// try to create a path there
1763 
1764 	for (INT16 edgepoint : *pEdgepoints)
1765 	{
1766 		sTempDist = PythSpacesAway(sGridNo, edgepoint);
1767 		if ( sTempDist < sClosestDist )
1768 		{
1769 			sClosestDist = sTempDist;
1770 			sClosestSpot = edgepoint;
1771 		}
1772 	}
1773 
1774 	return( sClosestSpot );
1775 }
1776 
FindNearestEdgePoint(INT16 sGridNo)1777 INT16 FindNearestEdgePoint( INT16 sGridNo )
1778 {
1779 	INT16 sScreenX, sScreenY, sMaxScreenX, sMaxScreenY;
1780 	INT16 sDist[5], sMinDist;
1781 	INT32 iLoop;
1782 	INT8  bMinIndex;
1783 	std::vector<INT16>* pEdgepoints;
1784 	INT16 sClosestSpot = NOWHERE, sClosestDist = 0x7FFF, sTempDist;
1785 
1786 	GetAbsoluteScreenXYFromMapPos(sGridNo, &sScreenX, &sScreenY);
1787 
1788 	sMaxScreenX = gsRightX - gsLeftX;
1789 	sMaxScreenY = gsBottomY - gsTopY;
1790 
1791 	sDist[0] = 0x7FFF;
1792 	sDist[1] = sScreenX;			// west
1793 	sDist[2] = sMaxScreenX - sScreenX;	// east
1794 	sDist[3] = sScreenY;			// north
1795 	sDist[4] = sMaxScreenY - sScreenY;	// south
1796 
1797 	sMinDist = sDist[0];
1798 	bMinIndex = 0;
1799 	for( iLoop = 1; iLoop < 5; iLoop++)
1800 	{
1801 		if ( sDist[ iLoop ] < sMinDist )
1802 		{
1803 			sMinDist = sDist[ iLoop ];
1804 			bMinIndex = (INT8) iLoop;
1805 		}
1806 	}
1807 
1808 	switch( bMinIndex )
1809 	{
1810 		case 1:
1811 			pEdgepoints = &gps1stWestEdgepointArray;
1812 			break;
1813 		case 2:
1814 			pEdgepoints = &gps1stEastEdgepointArray;
1815 			break;
1816 		case 3:
1817 			pEdgepoints = &gps1stNorthEdgepointArray;
1818 			break;
1819 		case 4:
1820 			pEdgepoints = &gps1stSouthEdgepointArray;
1821 			break;
1822 		default:
1823 			// WTF???
1824 			return( NOWHERE );
1825 	}
1826 
1827 	// Do a 2D search to find the closest map edgepoint and
1828 	// try to create a path there
1829 
1830 	for (INT16 edgepoint : *pEdgepoints)
1831 	{
1832 		sTempDist = PythSpacesAway(sGridNo, edgepoint);
1833 		if ( sTempDist < sClosestDist )
1834 		{
1835 			sClosestDist = sTempDist;
1836 			sClosestSpot = edgepoint;
1837 		}
1838 	}
1839 
1840 	return( sClosestSpot );
1841 }
1842 
1843 #define EDGE_OF_MAP_SEARCH 5
1844 
FindNearbyPointOnEdgeOfMap(SOLDIERTYPE * pSoldier,INT8 * pbDirection)1845 INT16 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection )
1846 {
1847 	INT32 iSearchRange;
1848 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1849 
1850 	INT16 sGridNo, sClosestSpot = NOWHERE;
1851 	INT8  bDirection, bClosestDirection;
1852 	INT32 iPathCost, iClosestPathCost = 1000;
1853 
1854 	bClosestDirection = -1;
1855 
1856 	// Call FindBestPath to set flags in all locations that we can
1857 	// walk into within range.  We have to set some things up first...
1858 
1859 	// set the distance limit of the square region
1860 	gubNPCDistLimit = EDGE_OF_MAP_SEARCH;
1861 
1862 	iSearchRange = EDGE_OF_MAP_SEARCH;
1863 
1864 	// determine maximum horizontal limits
1865 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1866 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1867 
1868 	// determine maximum vertical limits
1869 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1870 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1871 
1872 	// reset the "reachable" flags in the region we're looking at
1873 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1874 	{
1875 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1876 		{
1877 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1878 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1879 			{
1880 				continue;
1881 			}
1882 
1883 			gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1884 		}
1885 	}
1886 
1887 	FindBestPath( pSoldier, NOWHERE, pSoldier->bLevel, WALKING, COPYREACHABLE, 0 );
1888 
1889 	// Turn off the "reachable" flag for his current location
1890 	// so we don't consider it
1891 	gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
1892 
1893 	// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
1894 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1895 	{
1896 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1897 		{
1898 			// calculate the next potential gridno
1899 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1900 			if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
1901 			{
1902 				continue;
1903 			}
1904 
1905 			if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
1906 			{
1907 				continue;
1908 			}
1909 
1910 			if (GridNoOnEdgeOfMap( sGridNo, &bDirection ) )
1911 			{
1912 				iPathCost = PythSpacesAway( pSoldier->sGridNo, sGridNo );
1913 
1914 				if (iPathCost < iClosestPathCost)
1915 				{
1916 					// this place is closer
1917 					sClosestSpot = sGridNo;
1918 					iClosestPathCost = iPathCost;
1919 					bClosestDirection = bDirection;
1920 				}
1921 			}
1922 		}
1923 	}
1924 
1925 	*pbDirection = bClosestDirection;
1926 	return( sClosestSpot );
1927 }
1928 
1929 
FindClosestBoxingRingSpot(SOLDIERTYPE * pSoldier,BOOLEAN fInRing)1930 INT16 FindClosestBoxingRingSpot( SOLDIERTYPE * pSoldier, BOOLEAN fInRing )
1931 {
1932 	INT32 iSearchRange;
1933 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1934 
1935 	INT16 sGridNo, sClosestSpot = NOWHERE;
1936 	INT32 iDistance, iClosestDistance = 9999;
1937 
1938 	// set the distance limit of the square region
1939 	iSearchRange = 7;
1940 
1941 	// determine maximum horizontal limits
1942 	sMaxLeft  = MIN( iSearchRange, (pSoldier->sGridNo % MAXCOL));
1943 	sMaxRight = MIN( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
1944 
1945 	if (pSoldier->bTeam == OUR_TEAM && !fInRing)
1946 	{
1947 		// have player not go to the left of the ring
1948 		sMaxLeft = 0;
1949 	}
1950 
1951 	// determine maximum vertical limits
1952 	sMaxUp   = MIN( iSearchRange, (pSoldier->sGridNo / MAXROW));
1953 	sMaxDown = MIN( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
1954 
1955 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
1956 	{
1957 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
1958 		{
1959 			// calculate the next potential gridno
1960 			sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
1961 			UINT8	const room = GetRoom(sGridNo);
1962 			if (room == NO_ROOM) continue;
1963 
1964 			if ((fInRing  && room == BOXING_RING) ||
1965 				((!fInRing && room != BOXING_RING) && LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0)))
1966 			{
1967 				iDistance = ABS( sXOffset ) + ABS( sYOffset );
1968 				if (iDistance < iClosestDistance && WhoIsThere2(sGridNo, 0) == NULL)
1969 				{
1970 					sClosestSpot = sGridNo;
1971 					iClosestDistance = iDistance;
1972 				}
1973 			}
1974 		}
1975 	}
1976 
1977 	return( sClosestSpot );
1978 }
1979 
FindNearestOpenableNonDoor(INT16 sStartGridNo)1980 INT16 FindNearestOpenableNonDoor( INT16 sStartGridNo )
1981 {
1982 	INT32 iSearchRange;
1983 	INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
1984 
1985 	INT16 sGridNo, sClosestSpot = NOWHERE;
1986 	INT32 iDistance, iClosestDistance = 9999;
1987 
1988 	// set the distance limit of the square region
1989 	iSearchRange = 7;
1990 
1991 	// determine maximum horizontal limits
1992 	sMaxLeft  = MIN( iSearchRange, (sStartGridNo % MAXCOL));
1993 	sMaxRight = MIN( iSearchRange, MAXCOL - ((sStartGridNo % MAXCOL) + 1));
1994 
1995 	// determine maximum vertical limits
1996 	sMaxUp   = MIN( iSearchRange, (sStartGridNo / MAXROW));
1997 	sMaxDown = MIN( iSearchRange, MAXROW - ((sStartGridNo / MAXROW) + 1));
1998 
1999 	for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
2000 	{
2001 		for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
2002 		{
2003 			// calculate the next potential gridno
2004 			sGridNo = sStartGridNo + sXOffset + (MAXCOL * sYOffset);
2005 			FOR_EACH_STRUCTURE(pStructure, sGridNo, STRUCTURE_OPENABLE)
2006 			{
2007 				// skip any doors
2008 				if (pStructure->fFlags & STRUCTURE_ANYDOOR) continue;
2009 
2010 				// we have found a valid non-door openable structure
2011 				iDistance = CardinalSpacesAway( sGridNo, sStartGridNo );
2012 				if ( iDistance < iClosestDistance )
2013 				{
2014 					sClosestSpot = sGridNo;
2015 					iClosestDistance = iDistance;
2016 				}
2017 				break;
2018 			}
2019 		}
2020 	}
2021 	return( sClosestSpot );
2022 }
2023