1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  *
22  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "saga2/saga2.h"
28 #include "saga2/dispnode.h"
29 #include "saga2/tile.h"
30 #include "saga2/motion.h"
31 #include "saga2/player.h"
32 #include "saga2/cmisc.h"
33 #include "saga2/priqueue.h"
34 
35 namespace Saga2 {
36 
37 //  Defines to turn on visual debugging aids for path finder.
38 #define VISUAL1     0
39 //1 for paths considered
40 #define VISUAL2     0
41 //2 unused
42 #define VISUAL3     0
43 //3 unused
44 #define VISUAL4     0
45 //4 for fetchTileSection
46 #define VISUAL5     0
47 //5 for fetchSubMeta
48 #define VISUAL6     0
49 //6 for selectNearbySite
50 #define VISUAL7     0
51 //7 for checkPath
52 
53 #if VISUAL1 || VISUAL2 || VISUAL4 || VISUAL5 || VISUAL6 || VISUAL7
54 void TPLine(const TilePoint &start, const TilePoint &stop);
55 #endif
56 
57 /* ===================================================================== *
58    Imports
59  * ===================================================================== */
60 
61 extern WorldMapData         *mapList;
62 
63 /* ===================================================================== *
64    Types
65  * ===================================================================== */
66 
67 const int           searchCenter = 13,      // origin point of array
68                     searchDiameter = searchCenter * 2;
69 
70 const int           straightBaseCost    = 4,
71                     diagBaseCost        = 6,
72                     straightEasyCost    = straightBaseCost + 2,
73                     straightNormalCost  = straightBaseCost + 4,
74                     straightHardCost    = straightBaseCost + 8,
75                     diagEasyCost        = diagBaseCost + 3,
76                     diagNormalCost      = diagBaseCost + 6,
77                     diagHardCost        = diagBaseCost + 12;
78 
79 const int           subMetaSize         = 4,
80                     subMetaShift        = 2,
81                     subMetaMask         = subMetaSize - 1;
82 
83 
84 
85 static StaticTilePoint tDirTable[8] = {
86 	{ 4,  4, 0},
87 	{ 0,  4, 0},
88 	{-4,  4, 0},
89 	{-4,  0, 0},
90 	{-4, -4, 0},
91 	{ 0, -4, 0},
92 	{ 4, -4, 0},
93 	{ 4,  0, 0}
94 };
95 
96 static StaticTilePoint tDirTable2[8] = {
97 	{ 1,  1, 0},
98 	{ 0,  1, 0},
99 	{-1,  1, 0},
100 	{-1,  0, 0},
101 	{-1, -1, 0},
102 	{ 0, -1, 0},
103 	{ 1, -1, 0},
104 	{ 1,  0, 0}
105 };
106 
107 static StaticTilePoint tDirTable3[8] = {
108 	{ 16,  16, 0},
109 	{  0,  16, 0},
110 	{-16,  16, 0},
111 	{-16,   0, 0},
112 	{-16, -16, 0},
113 	{  0, -16, 0},
114 	{ 16, -16, 0},
115 	{ 16,   0, 0}
116 };
117 
118 
119 
120 /* ===================================================================== *
121    The PathTileRegion class
122  * ===================================================================== */
123 
124 struct PathTileInfo {
125 	TileInfo    *surfaceTile;
126 	int16       surfaceHeight;
127 
PathTileInfoSaga2::PathTileInfo128 	PathTileInfo() : surfaceTile(nullptr), surfaceHeight(0) {}
129 };
130 
131 typedef PathTileInfo        PathTilePosInfo[maxPlatforms];
132 
133 typedef PathTilePosInfo     PathTilePosArray
134 [searchDiameter + 4]
135 [searchDiameter + 4];
136 
137 typedef uint8               PathSubMetaFlags
138 [((((searchDiameter
139      +   subMetaMask + 4)
140     * (searchDiameter
141     +   subMetaMask + 4))
142    >>  subMetaShift)
143   +   7)
144  >>  3];
145 
146 
147 //  This class manages an array containing terrain information, which
148 //  will be loaded on the fly as the path finder needs it.
149 class PathTileRegion {
150 
151 	friend class PathRequest;
152 
153 	int16           mapNum;         //  The map number of the terrain data
154 
155 	TilePoint       origin,         //  The base tile coords of the array
156 	                area,           //  The size of the array in tiles
157 	                subMetaArea;    //  The number of submetatiles overlaying
158 	//  the array area
159 	PathTilePosInfo *array;         //  The pointer to the array
160 	uint8           *subMetaFlags;  //  A bit array indicating which
161 	//  submetatiles have been loaded
162 
163 	void fetchSubMeta(const TilePoint &subMeta);
164 
165 public:
166 
PathTileRegion()167 	PathTileRegion() {
168 		mapNum = 0;
169 		array = nullptr;
170 		subMetaFlags = nullptr;
171 	}
172 
173 	void init(
174 	    int16           map,
175 	    const TilePoint &org,
176 	    const TilePoint &a,
177 	    PathTilePosInfo *arr,
178 	    uint8           *flagArray);
179 
180 	void fetchTileSection(const TilePoint &org, const TilePoint &a);
tilePos(const TilePoint & pos)181 	PathTilePosInfo *tilePos(const TilePoint &pos) {
182 		assert(pos.u >= origin.u && (pos.u - origin.u) < area.u);
183 		assert(pos.v >= origin.v && (pos.v - origin.v) < area.v);
184 		return &array[(pos.u - origin.u) * area.v + pos.v - origin.v];
185 	}
186 };
187 
188 
189 //  Initialize the path tile region
init(int16 map,const TilePoint & org,const TilePoint & a,PathTilePosInfo * arr,uint8 * flagArray)190 void PathTileRegion::init(
191     int16           map,
192     const TilePoint &org,
193     const TilePoint &a,
194     PathTilePosInfo *arr,
195     uint8           *flagArray) {
196 	mapNum = map;
197 	origin = org;
198 	area = a;
199 	array = arr;
200 	subMetaFlags = flagArray;
201 
202 	origin.z = area.z = subMetaArea.z = 0;
203 	subMetaArea.u = (area.u + (origin.u & subMetaMask) + subMetaMask)
204 	                >> subMetaShift;
205 	subMetaArea.v = (area.v + (origin.v & subMetaMask) + subMetaMask)
206 	                >> subMetaShift;
207 
208 	//  clear all of the submetatile flags
209 	memset(subMetaFlags, 0, (subMetaArea.u * subMetaArea.v + 7) >> 3);
210 
211 	//  nullptr the tile pointers in the array
212 	int16           arraySize = area.u * area.v;
213 	PathTilePosInfo *tiPtr = array;
214 	for (; arraySize > 0; arraySize--, tiPtr++) {
215 		PathTilePosInfo &ptpi = *tiPtr;
216 		for (int i = 0; i < maxPlatforms; i++)
217 			ptpi[i].surfaceTile = nullptr;
218 	}
219 }
220 
221 
222 //  This function will load the submetatiles which overlay a specified
223 //  region into the array, if those submetatiles have not already been
224 //  loaded.
fetchTileSection(const TilePoint & org,const TilePoint & a)225 void PathTileRegion::fetchTileSection(const TilePoint &org,
226                                       const TilePoint &a) {
227 	int16       flagIndex;
228 	int       u, v;
229 	TilePoint   secSubMetaOrigin,
230 	            secSubMetaArea,
231 	            relSubMeta;
232 
233 #if VISUAL4
234 	TilePoint   pt1, pt2;
235 	pt1 = pt2 = org << kTileUVShift;
236 	pt1.z = pt2.z = 0;
237 	pt2.u += (a.u << kTileUVShift);
238 	TPLine(pt1, pt2);
239 	pt1.u = pt2.u;
240 	pt1.v += (a.v << kTileUVShift);
241 	TPLine(pt1, pt2);
242 	pt2.v = pt1.v;
243 	pt2.u -= (a.u << kTileUVShift);
244 	TPLine(pt1, pt2);
245 	pt1.u = pt2.u;
246 	pt1.v -= (a.v << kTileUVShift);
247 	TPLine(pt1, pt2);
248 #endif
249 
250 	//  Determine the origin and area of the specified tile section
251 	//  in submetatile coordinates
252 	secSubMetaOrigin.u = (org.u >> subMetaShift);
253 	secSubMetaOrigin.v = (org.v >> subMetaShift);
254 	secSubMetaArea.u = (a.u + (org.u & subMetaMask) + subMetaMask)
255 	                   >> subMetaShift;
256 	secSubMetaArea.v = (a.v + (org.v & subMetaMask) + subMetaMask)
257 	                   >> subMetaShift;
258 
259 	for (u = 0; u < secSubMetaArea.u; u++) {
260 		relSubMeta.u = secSubMetaOrigin.u - (origin.u >> subMetaShift) + u;
261 		relSubMeta.v = secSubMetaOrigin.v - (origin.v >> subMetaShift);
262 		flagIndex = relSubMeta.u * subMetaArea.v + relSubMeta.v;
263 
264 		for (v = 0; v < secSubMetaArea.v; v++, flagIndex++) {
265 			//  Check the submetatile flag in the bit array
266 			if (!(subMetaFlags[flagIndex >> 3] & (1 << (flagIndex & 7)))) {
267 				//  Load the submetatile and set its flag
268 				fetchSubMeta(TilePoint(secSubMetaOrigin.u + u,
269 				                       secSubMetaOrigin.v + v,
270 				                       0));
271 				subMetaFlags[flagIndex >> 3] |= 1 << (flagIndex & 7);
272 			}
273 		}
274 	}
275 }
276 
277 
278 //  This function will load a submeta tile in the array
fetchSubMeta(const TilePoint & subMeta)279 void PathTileRegion::fetchSubMeta(const TilePoint &subMeta) {
280 	WorldMapData    *map = &mapList[mapNum];
281 
282 	TilePoint       mCoords;
283 	MetaTile        *mt;
284 
285 	mCoords.u = subMeta.u >> 1;
286 	mCoords.v = subMeta.v >> 1;
287 	mCoords.z = 0;
288 	mt = map->lookupMeta(mCoords);
289 
290 #if VISUAL5
291 	TilePoint   pt1, pt2;
292 	pt1 = pt2 = subMeta << (subMetaShift + kTileUVShift);
293 	pt1.z = pt2.z = 0;
294 	pt2.u += (subMetaSize << kTileUVShift);
295 	TPLine(pt1, pt2);
296 	pt1.u = pt2.u;
297 	pt1.v += (subMetaSize << kTileUVShift);
298 	TPLine(pt1, pt2);
299 	pt2.v = pt1.v;
300 	pt2.u -= (subMetaSize << kTileUVShift);
301 	TPLine(pt1, pt2);
302 	pt1.u = pt2.u;
303 	pt1.v -= (subMetaSize << kTileUVShift);
304 	TPLine(pt1, pt2);
305 #endif
306 
307 	if (mt) {
308 		TileRegion  tileReg;
309 		TilePoint   offset;
310 
311 		tileReg.min.u = (subMeta.u << subMetaShift) - origin.u;
312 		offset.u =  tileReg.min.u + subMetaSize < area.u
313 		            ?   subMetaSize
314 		            :   area.u - tileReg.min.u;
315 		tileReg.min.v = (subMeta.v << subMetaShift) - origin.v;
316 		offset.v =  tileReg.min.v + subMetaSize < area.v
317 		            ?   subMetaSize
318 		            :   area.v - tileReg.min.v;
319 
320 		if (tileReg.min.u < 0) {
321 			offset.u += tileReg.min.u;
322 			tileReg.min.u = 0;
323 		}
324 		if (tileReg.min.v < 0) {
325 			offset.v += tileReg.min.v;
326 			tileReg.min.v = 0;
327 		}
328 
329 		//  Compute tile region relative to metatile
330 		tileReg.min.u = (tileReg.min.u + origin.u) & kPlatMask;
331 		tileReg.max.u = tileReg.min.u + offset.u;
332 		tileReg.min.v = (tileReg.min.v + origin.v) & kPlatMask;
333 		tileReg.max.v = tileReg.min.v + offset.v;
334 
335 		assert(tileReg.max.u <= kPlatformWidth);
336 		assert(tileReg.max.v <= kPlatformWidth);
337 
338 		//  Compute the offset of base tile in metatile to origin
339 		offset.u = ((subMeta.u >> 1) << kPlatShift) - origin.u;
340 		offset.v = ((subMeta.v >> 1) << kPlatShift) - origin.v;
341 
342 		for (int i = 0; i < maxPlatforms; i++) {
343 			uint16      tpFlags = 0;
344 			Platform    *p;
345 			int       u, v;
346 			TileRef     *tr;
347 			int16       height;
348 
349 			if ((p = mt->fetchPlatform(mapNum, i)) == nullptr)
350 				continue;
351 
352 			if (!(p->flags & plVisible)) continue;
353 
354 			for (u = tileReg.min.u; u < tileReg.max.u; u++) {
355 				PathTilePosInfo *arrRow = &array[(u + offset.u) * area.v];
356 
357 				assert(u >= 0);
358 				assert(u < kPlatformWidth);
359 
360 				for (v = tileReg.min.v; v < tileReg.max.v; v++) {
361 					int16   flagIndex = ((u & subMetaMask) << subMetaShift) | (v & subMetaMask);
362 
363 					assert(v >= 0);
364 					assert(v < kPlatformWidth);
365 
366 					if (!(tpFlags & (1 << flagIndex))) {
367 						tpFlags |= (1 << flagIndex);
368 
369 						tr = &p->tiles[u][v];
370 						height = tr->tileHeight << 3;
371 
372 						if (tr->flags & trTileTAG) {
373 							ActiveItem  *groupItem,
374 							            *instanceItem;
375 							int16       state = 0;
376 							int16       tagU, tagV;
377 							int            tempU, tempV;
378 							TilePoint   absPos;
379 							TileRegion  subMetaTag;
380 							TileRef     *stateData;
381 
382 							assert((uint16)tr->tile <= activeItemIndexNullID);
383 							groupItem = ActiveItem::activeItemAddress(
384 							                ActiveItemID(mapNum, tr->tile));
385 
386 							tagU = u - ((tr->flags >> 1) & 0x07);
387 							tagV = v - ((tr->flags >> 4) & 0x07);
388 
389 							subMetaTag.min.u = tagU;
390 							subMetaTag.max.u = tagU + groupItem->_data.group.uSize;
391 							subMetaTag.min.v = tagV;
392 							subMetaTag.max.v = tagV + groupItem->_data.group.vSize;
393 
394 							if (subMetaTag.min.u < tileReg.min.u)
395 								subMetaTag.min.u = tileReg.min.u;
396 							if (subMetaTag.max.u > tileReg.max.u)
397 								subMetaTag.max.u = tileReg.max.u;
398 							if (subMetaTag.min.v < tileReg.min.v)
399 								subMetaTag.min.v = tileReg.min.v;
400 							if (subMetaTag.max.v > tileReg.max.v)
401 								subMetaTag.max.v = tileReg.max.v;
402 
403 							//  Abspos is the absolute position of the
404 							//  group on the tile map.
405 
406 							absPos.u = (mCoords.u << kPlatShift) | tagU;
407 							absPos.v = (mCoords.v << kPlatShift) | tagV;
408 							absPos.z = height;
409 
410 							//  Look up the group instance in the hash.
411 							instanceItem = map->findHashedInstance(absPos, tr->tile);
412 							if (instanceItem) state = instanceItem->getInstanceState(mapNum);
413 
414 							stateData = &(map->activeItemData)[
415 							                groupItem->_data.group.grDataOffset
416 							                +   state * groupItem->_data.group.animArea];
417 
418 							for (tempU = subMetaTag.min.u; tempU < subMetaTag.max.u; tempU++) {
419 								TileRef         *rowData = &stateData[(tempU - tagU) * groupItem->_data.group.vSize];
420 								PathTilePosInfo *tempArrRow = &array[(tempU + offset.u) * area.v];
421 
422 								for (tempV = subMetaTag.min.v; tempV < subMetaTag.max.v; tempV++) {
423 									flagIndex = ((tempU & subMetaMask) << subMetaShift) + (tempV & subMetaMask);
424 
425 									tpFlags |= (1 << flagIndex);
426 
427 									if (instanceItem) tr = &rowData[tempV - tagV];
428 #if DEBUG
429 									else {
430 										static  TileRef dummyRef = { 1, 0, 0 };
431 										tr = &dummyRef;
432 									}
433 #endif
434 									tempArrRow[tempV + offset.v][i].surfaceTile =
435 									    TileInfo::tileAddress(tr->tile);
436 									tempArrRow[tempV + offset.v][i].surfaceHeight =
437 									    height + (tr->tileHeight << 3);
438 								}
439 							}
440 						} else {
441 							arrRow[v + offset.v][i].surfaceTile =
442 							    TileInfo::tileAddress(tr->tile);
443 							arrRow[v + offset.v][i].surfaceHeight =
444 							    height;
445 						}
446 					}
447 				}
448 			}
449 		}
450 	}
451 }
452 
453 
454 /* ===================================================================== *
455    The PathArray and QueueItem types
456  * ===================================================================== */
457 
458 //  Represents a single cell on the path array
459 
460 struct PathCell {
461 	uint8           direction;              // direction into cell
462 	int8            platformDelta;          // platform difference of cell
463 	int16           height;                 // height above floor
464 	int16           cost;                   // cost to get there
465 };
466 
467 //  Virtual array of cells representing the path region we will search.
468 class PathArray {
469 public:
470 
471 	enum {
472 		chunkTileDiameter = 4,
473 		regionChunkDiameter = (searchDiameter + chunkTileDiameter - 1) / chunkTileDiameter
474 	};
475 
476 private:
477 	//  Search sub-region class.
478 	struct PathArrayChunk {
479 		uint16      mask;       //  Mask indicating which cells in chunk are
480 		//  allocated
481 		//  The cell array
482 		PathCell    array[chunkTileDiameter][chunkTileDiameter];
483 
PathArrayChunkSaga2::PathArray::PathArrayChunk484 		PathArrayChunk(void) : mask(0) {}
485 	};
486 
487 	//  Master array of chunk pointers
488 	PathArrayChunk  *array[maxPlatforms][regionChunkDiameter][regionChunkDiameter];
489 public:
490 
491 	//  Exception class
492 	class CellAllocationFailure {};
493 
494 	//  Constructor
495 	PathArray(void);
496 
497 	//  Destructor
498 	~PathArray(void);
499 
500 	//  Make a new cell or access an existing cell.  If the specified
501 	//  cell already exists *newCell will be set to false, else it will
502 	//  be true.  If it fails to allocate a new cell it will throw
503 	//  a CellAllocationFailure.
504 	PathCell *makeCell(int plat, int uCoord, int vCoord, bool *newCell);
505 
506 	//  Get a pointer to an existing cell.  If the specified cell has
507 	//  not been created, it will return nullptr.
508 	PathCell *getCell(int plat, int uCoord, int vCoord);
509 
510 	//  Delete an existing cell
511 	void deleteCell(int plat, int uCoord, int vCoord);
512 
513 	//  Delete all existing cells
514 	void reset(void);
515 };
516 
517 //  Constructor
PathArray(void)518 PathArray::PathArray(void) {
519 	int     plat, chunkU, chunkV;
520 
521 	for (plat = 0; plat < maxPlatforms; plat++) {
522 		for (chunkU = 0; chunkU < regionChunkDiameter; chunkU++) {
523 			for (chunkV = 0; chunkV < regionChunkDiameter; chunkV++)
524 				array[plat][chunkU][chunkV] = nullptr;
525 		}
526 	}
527 }
528 
529 //  Destructor
~PathArray(void)530 PathArray::~PathArray(void) {
531 	reset();
532 }
533 
534 
535 //  Make a new cell or access an existing cell.  If the specified
536 //  cell already exists *newCell will be set to false, else it will
537 //  be true.  If it fails to allocate a new cell it will throw
538 //  a CellAllocationFailure.
makeCell(int plat,int uCoord,int vCoord,bool * newCell)539 PathCell *PathArray::makeCell(int plat, int uCoord, int vCoord, bool *newCell) {
540 	assert(plat >= 0 && plat < maxPlatforms);
541 	assert(uCoord >= 0 && uCoord < searchDiameter);
542 	assert(vCoord >= 0 && vCoord < searchDiameter);
543 	assert(newCell != nullptr);
544 
545 	//  Compute the chunk coords
546 	int             chunkUCoord = uCoord >> 2,
547 	                chunkVCoord = vCoord >> 2;
548 
549 	//  Get a pointer to the chunk pointer in the array
550 	PathArrayChunk  **chunkPtrPtr = &array[plat][chunkUCoord][chunkVCoord];
551 
552 	//  Get existing chunk or allocate a new one
553 	if (*chunkPtrPtr != nullptr || (*chunkPtrPtr = new PathArrayChunk) !=  nullptr) {
554 		PathArrayChunk  *chunkPtr = *chunkPtrPtr;
555 		uint16          chunkCellMask;
556 
557 		//  Compute the coordinates of the cell relative to the chunk
558 		uCoord &= chunkTileDiameter - 1;
559 		vCoord &= chunkTileDiameter - 1;
560 
561 		//  Compute the bit mask of the cell within the chunk
562 		chunkCellMask = 1 << ((uCoord << 2) | vCoord);
563 
564 		//  Determine if this is a new cell
565 		*newCell = (chunkPtr->mask & chunkCellMask) == 0;
566 
567 		//  Mark the cell as allocated
568 		chunkPtr->mask |= chunkCellMask;
569 
570 		return &chunkPtr->array[uCoord][vCoord];
571 	} else {
572 		//  Failed to allocate cell so throw exception
573 		error("Cell Allocation failure");
574 	}
575 }
576 
577 //  Get a pointer to an existing cell.  If the specified cell has
578 //  not been created, it will return nullptr.
getCell(int plat,int uCoord,int vCoord)579 PathCell *PathArray::getCell(int plat, int uCoord, int vCoord) {
580 	assert(plat >= 0 && plat < maxPlatforms);
581 	assert(uCoord >= 0 && uCoord < searchDiameter);
582 	assert(vCoord >= 0 && vCoord < searchDiameter);
583 
584 	//  Compute the chunk coords
585 	int             chunkUCoord = uCoord >> 2,
586 	                chunkVCoord = vCoord >> 2;
587 	uint16          chunkCellMask;
588 
589 	PathArrayChunk  *chunkPtr = array[plat][chunkUCoord][chunkVCoord];
590 
591 	if (chunkPtr == nullptr) return nullptr;
592 
593 	//  Compute the coordinates of the cell relative to the chunk
594 	uCoord &= chunkTileDiameter - 1;
595 	vCoord &= chunkTileDiameter - 1;
596 
597 	//  Compute the bit mask of the cell within the chunk
598 	chunkCellMask = 1 << ((uCoord << 2) | vCoord);
599 
600 	//  Determine if cell has been allocated
601 	if ((chunkPtr->mask & chunkCellMask) == 0)
602 		return nullptr;
603 
604 	return &chunkPtr->array[uCoord][vCoord];
605 }
606 
deleteCell(int plat,int uCoord,int vCoord)607 void PathArray::deleteCell(int plat, int uCoord, int vCoord) {
608 	assert(plat >= 0 && plat < maxPlatforms);
609 	assert(uCoord >= 0 && uCoord < searchDiameter);
610 	assert(vCoord >= 0 && vCoord < searchDiameter);
611 
612 	//  Compute the chunk coords
613 	int             chunkUCoord = uCoord >> 2,
614 	                chunkVCoord = vCoord >> 2;
615 	uint16          chunkCellMask;
616 
617 	PathArrayChunk  *chunkPtr = array[plat][chunkUCoord][chunkVCoord];
618 
619 	if (chunkPtr == nullptr)
620 		return;
621 
622 	//  Compute the coordinates of the cell relative to the chunk
623 	uCoord &= chunkTileDiameter - 1;
624 	vCoord &= chunkTileDiameter - 1;
625 
626 	//  Compute the bit mask of the cell within the chunk
627 	chunkCellMask = 1 << ((uCoord << 2) | vCoord);
628 
629 	//  Clear the bit
630 	chunkPtr->mask &= ~chunkCellMask;
631 }
632 
633 //  Delete all existing cells
reset(void)634 void PathArray::reset(void) {
635 	int     plat, chunkU, chunkV;
636 
637 	for (plat = 0; plat < maxPlatforms; plat++) {
638 		for (chunkU = 0; chunkU < regionChunkDiameter; chunkU++) {
639 			for (chunkV = 0; chunkV < regionChunkDiameter; chunkV++) {
640 				PathArrayChunk      **chunkPtrPtr;
641 
642 				chunkPtrPtr = &array[plat][chunkU][chunkV];
643 
644 				if (*chunkPtrPtr != nullptr) {
645 					delete *chunkPtrPtr;
646 					*chunkPtrPtr = nullptr;
647 				}
648 			}
649 		}
650 	}
651 }
652 
653 //  Represents an entry in the queue
654 
655 struct QueueItem {
656 	int16           z;                      // height over terrain
657 	uint8           u, v;                   // relative coords of cell
658 	uint8           platform;               // platform number of cell
659 	Direction       direction;              // direction out of cell
660 	uint8           pad;
661 	int16           cost;                   // Cost to get to this cell
662 
QueueItemSaga2::QueueItem663 	QueueItem() {
664 		z = 0;
665 		u = v = 0;
666 		platform = 0;
667 		pad = 0;
668 		cost = 0;
669 		direction = 0;
670 	}
671 
operator intSaga2::QueueItem672 	operator int() {
673 		return cost;
674 	}
675 };
676 
677 
678 /* ===================================================================== *
679    The MaskComputer class
680  * ===================================================================== */
681 
682 //  Represents the subtile mask of an object at a single point
683 struct PointMask {
684 	TilePoint   size;
685 	TilePoint   offset;
686 	uint16      mask[16];
687 };
688 
689 
690 //  Represents the subtile masks of each point on a path in a single
691 //  direction
692 class DirMask {
693 	friend class DirMaskGroup;
694 
695 	PointMask pathPt[4];
696 
697 public:
operator [](int16 index)698 	PointMask &operator[](int16 index) {
699 		return pathPt[index];
700 	}
701 };
702 
703 
704 //  Represents the subtile masks of each path in all eight direction
705 class DirMaskGroup {
706 	friend class MaskComputer;
707 
708 	uint8   crossSection;
709 	DirMask dMask[8];
710 
711 	void computeMask(uint8 objSection);
712 
713 public:
DirMaskGroup()714 	DirMaskGroup() : crossSection(0) {}
operator [](int16 index)715 	DirMask &operator[](int16 index) {
716 		return dMask[index];
717 	}
718 };
719 
720 
721 //  The class which will compute all of the subtile masks for an object
722 class MaskComputer {
723 
724 private:
725 	DirMaskGroup    array[8],
726 	                *ptrArray[8];
727 	int16           arraySize;
728 
729 public:
MaskComputer(void)730 	MaskComputer(void) : arraySize(0) {
731 		for (int i = 0; i < 8; i++)
732 			ptrArray[i] = nullptr;
733 	}
734 
735 	DirMaskGroup *computeMask(uint8 objSection);
736 };
737 
738 
739 //  return a word with the bit (minBit) to bit (maxBit-1) all set,
740 //  and all other bits reset.
741 
makeMask16(int minBit,int maxBit)742 inline uint32 makeMask16(int minBit, int maxBit) {
743 	return (1 << maxBit) - (1 << minBit);
744 }
745 
computeMask(uint8 objSection)746 void DirMaskGroup::computeMask(uint8 objSection) {
747 	bool        diagExpand;
748 	int       dir;
749 	struct {
750 		int16   min;
751 		int16   max;
752 	}           area;
753 
754 	crossSection = objSection;
755 
756 	//  Calculate the area in subtiles the object occupies.  Since U and
757 	//  V coordinates will alway equal each other, there is no need to
758 	//  calculate both.
759 	area.min = ((kTileUVSize / 2) - objSection) >> kSubTileShift;
760 	area.max = ((kTileUVSize / 2) + objSection + kSubTileMask) >> kSubTileShift;
761 
762 	//  Determine if the cross section is wide enough that the diaginal
763 	//  masks need to be expanded outward one subtile
764 	diagExpand = (objSection - 1) & 0x2;
765 
766 	for (dir = 0; dir < 8; dir++) {
767 		int           ptNum;
768 		TileRegion  baseMaskArea;
769 
770 		//  Calculate the base mask from which all of the point masks
771 		//  will be derived
772 		baseMaskArea.min.u = baseMaskArea.min.v = area.min;
773 		baseMaskArea.max.u = baseMaskArea.max.v = area.max;
774 
775 		if (!(dir & 0x1) && diagExpand) {
776 			switch (dir >> 1) {
777 			case 0:
778 				baseMaskArea.min.u--;
779 				baseMaskArea.max.v--;
780 				break;
781 			case 1:
782 				baseMaskArea.max.u++;
783 				baseMaskArea.min.v--;
784 				break;
785 			case 2:
786 				baseMaskArea.max.u++;
787 				baseMaskArea.max.v++;
788 				break;
789 			case 3:
790 				baseMaskArea.min.u--;
791 				baseMaskArea.max.v++;
792 			}
793 		}
794 
795 		for (ptNum = 0; ptNum < 4; ptNum++) {
796 			int       u,
797 			          v;
798 			TileRegion  ptMaskArea;
799 			uint16      tempMask[16];
800 			PointMask   *ptMask = &dMask[dir].pathPt[ptNum];
801 
802 			//  Compute the point mask area
803 			ptMaskArea.min = baseMaskArea.min + tDirTable2[dir] * (ptNum + 1);
804 			ptMaskArea.max = baseMaskArea.max + tDirTable2[dir] * (ptNum + 1);
805 
806 			ptMask->offset.u = ptMaskArea.min.u >> kTileSubShift;
807 			ptMask->offset.v = ptMaskArea.min.v >> kTileSubShift;
808 
809 			ptMaskArea.max.u -= ptMaskArea.min.u & ~kSubTileMask;
810 			ptMaskArea.min.u &= kSubTileMask;
811 			ptMaskArea.max.v -= ptMaskArea.min.v & ~kSubTileMask;
812 			ptMaskArea.min.v &= kSubTileMask;
813 
814 			ptMask->size.u = (ptMaskArea.max.u + kTileSubMask) >> kTileSubShift;
815 			ptMask->size.v = (ptMaskArea.max.v + kTileSubMask) >> kTileSubShift;
816 
817 			memset(tempMask, 0, sizeof(tempMask));
818 
819 			uint16  vMask = makeMask16(ptMaskArea.min.v, ptMaskArea.max.v);
820 			for (u = ptMaskArea.min.u; u < ptMaskArea.max.u; u++)
821 				tempMask[u] = vMask;
822 
823 			for (u = 0; u < ptMask->size.u; u++) {
824 				uint16  *srcMask = &tempMask[u << 2];
825 				uint16  *destMask = &ptMask->mask[u << 2];
826 
827 				for (v = 0; v < ptMask->size.v; v++) {
828 					switch (v) {
829 					case 0:
830 						destMask[0] = (srcMask[0] & 0x000f)       |
831 						                (srcMask[1] & 0x000f) <<  4 |
832 						                (srcMask[2] & 0x000f) <<  8 |
833 						                (srcMask[3] & 0x000f) << 12;
834 						break;
835 
836 					case 1:
837 						destMask[1] = (srcMask[0] & 0x00f0) >>  4 |
838 						                (srcMask[1] & 0x00f0)       |
839 						                (srcMask[2] & 0x00f0) <<  4 |
840 						                (srcMask[3] & 0x00f0) <<  8;
841 						break;
842 
843 					case 2:
844 
845 						destMask[2] = (srcMask[0] & 0x0f00) >>  8 |
846 						                (srcMask[1] & 0x0f00) >>  4 |
847 						                (srcMask[2] & 0x0f00)       |
848 						                (srcMask[3] & 0x0f00) <<  4;
849 						break;
850 
851 					case 3:
852 						destMask[3] = (srcMask[0] & 0xf000) >> 12 |
853 						                (srcMask[1] & 0xf000) >>  8 |
854 						                (srcMask[2] & 0xf000) >>  4 |
855 						                (srcMask[3] & 0xf000);
856 					}
857 				}
858 			}
859 		}
860 	}
861 }
862 
computeMask(uint8 crossSection)863 DirMaskGroup *MaskComputer::computeMask(uint8 crossSection) {
864 	DirMaskGroup    *maskGroup;
865 	int             i;
866 
867 	//  Check if this mask group has already been computed
868 	for (i = 0; i < arraySize; i++) {
869 		maskGroup = ptrArray[i];
870 
871 		if (maskGroup->crossSection == crossSection) {
872 			//  This mask group has already been computed
873 			if (i > 0) {
874 				//  Move the reference to this mask group up one position
875 				ptrArray[i] = ptrArray[i - 1];
876 				ptrArray[i - 1] = maskGroup;
877 			}
878 
879 			return maskGroup;
880 		}
881 	}
882 
883 	if (arraySize < ARRAYSIZE(array)) {
884 		//  Allocate a new place for this mask group
885 		maskGroup = ptrArray[arraySize] = &array[arraySize];
886 		arraySize++;
887 	} else
888 		//  Discard last referenced mask group in array
889 		maskGroup = ptrArray[ARRAYSIZE(array) - 1];
890 
891 	//  Compute the new group of masks
892 	maskGroup->computeMask(crossSection);
893 
894 	return maskGroup;
895 }
896 
897 
tileTerrain(PathTilePosInfo * tilePos,int16 mask,int16 minZ,int16 maxZ)898 uint32 tileTerrain(
899     PathTilePosInfo *tilePos,
900     int16           mask,
901     int16           minZ,
902     int16           maxZ) {
903 	uint32          terrain = 0;
904 
905 	for (int i = 0; i < maxPlatforms; i++) {
906 		int32           height, tileMinZ, tileMaxZ;
907 		TileInfo        *ti;
908 
909 		ti = (*tilePos)[i].surfaceTile;
910 
911 		if (ti) {
912 			height = (*tilePos)[i]. surfaceHeight;
913 			TileAttrs &attrs = ti->attrs;
914 			tileMinZ = tileMaxZ = height;
915 			int32   combinedMask = ti->combinedTerrainMask();
916 
917 			if (combinedMask & terrainRaised)
918 				tileMaxZ += attrs.terrainHeight;
919 			if (combinedMask & terrainWater)
920 				tileMinZ -= attrs.terrainHeight;
921 
922 			if (tileMinZ <  maxZ
923 			        &&  tileMaxZ >= minZ) {
924 				uint32  terrainResult = 0,
925 				        tileFgdTerrain = (1 << attrs.fgdTerrain),
926 				        tileBgdTerrain = (1 << attrs.bgdTerrain);
927 
928 				//  If only checking the top of raised terrain treat it
929 				//  as if it were normal terrain.
930 				if (minZ + kMaxStepHeight >= tileMaxZ) {
931 					if (tileFgdTerrain & terrainSupportingRaised)
932 						tileFgdTerrain = terrainNormal;
933 					if (tileBgdTerrain & terrainSupportingRaised)
934 						tileBgdTerrain = terrainNormal;
935 				}
936 
937 				if (mask & attrs.terrainMask)
938 					terrainResult |= tileFgdTerrain;
939 
940 				if (mask & ~attrs.terrainMask)
941 					terrainResult |= tileBgdTerrain;
942 
943 				//  This prevents actors from walking through
944 				//  catwalks and other surfaces which have no bottom.
945 
946 				if ((terrainResult & terrainSolidSurface)
947 				        &&  height > minZ + kMaxStepHeight) {
948 					terrainResult |= terrainStone;
949 				}
950 
951 				terrain |= terrainResult;
952 			}
953 		}
954 	}
955 	return terrain;
956 }
957 
958 
959 const uint32 terrainSink = terrainInsubstantial | terrainSupportingRaised | terrainWater;
960 
tileSlopeHeight(PathTileRegion & tileArr,const TilePoint & pt,GameObject * obj,PathTileInfo * ptiResult,uint8 * platformResult)961 int16 tileSlopeHeight(
962     PathTileRegion  &tileArr,
963     const TilePoint &pt,
964     GameObject      *obj,
965     PathTileInfo    *ptiResult,
966     uint8           *platformResult) {
967 	//  Calculate coordinates of tile and subtile
968 	TilePoint       tileCoords = pt >> kTileUVShift,
969 	                subTile(
970 	                    (pt.u >> kSubTileShift) & kSubTileMask,
971 	                    (pt.v >> kSubTileShift) & kSubTileMask,
972 	                    0);
973 
974 	PathTileInfo    highestTile,
975 	                lowestTile;
976 	int             supportHeight,
977 	                highestSupportHeight,
978 	                lowestSupportHeight;
979 	uint32
980 	highestSupportPlatform = 0,
981 	lowestSupportPlatform = 0;
982 	bool            highestTileFlag,
983 	                lowestTileFlag;
984 	PathTilePosInfo &tilePosInfo = *tileArr.tilePos(tileCoords);
985 
986 	highestSupportHeight = -100;
987 	lowestSupportHeight = 0x7FFF;
988 	highestTileFlag = false;
989 	lowestTileFlag = false;
990 	int objProtHt = obj->proto()->height;
991 
992 	//  Search each platform until we find a tile which is under
993 	//  the character.
994 
995 	for (int i = 0; i < maxPlatforms; i++) {
996 		PathTileInfo    *pti = ((PathTileInfo *)(&tilePosInfo)) + i; // &tilePosInfo[i];
997 		TileInfo        *ti = pti->surfaceTile;
998 
999 		if (ti) {
1000 			int     height = pti->surfaceHeight;
1001 			TileAttrs &attrs = ti->attrs;
1002 			int16   tileBase = height;
1003 			int32 subTileTerrain =
1004 			    attrs.testTerrain(calcSubTileMask(subTile.u,
1005 			                                      subTile.v));
1006 			if (subTileTerrain & terrainSink) {
1007 				if (subTileTerrain & terrainInsubstantial)
1008 					continue;
1009 				else if (subTileTerrain & terrainSupportingRaised)
1010 					// calculate height of raised surface
1011 					supportHeight = height +
1012 					                attrs.terrainHeight;
1013 				else {
1014 					// calculate depth of water
1015 					supportHeight = height -
1016 					                attrs.terrainHeight;
1017 					tileBase = supportHeight;
1018 				}
1019 			} else
1020 				// calculate height of unraised surface
1021 				supportHeight = height +
1022 				                ptHeight(TilePoint(pt.u & kTileUVMask,
1023 				                                   pt.v & kTileUVMask,
1024 				                                   0),
1025 				                         attrs.cornerHeight);
1026 
1027 			//  See if the tile is a potential supporting surface
1028 			if (tileBase < pt.z + objProtHt
1029 			        &&  supportHeight >= highestSupportHeight
1030 			        && (ti->combinedTerrainMask() & (terrainSurface | terrainRaised))) {
1031 				highestTileFlag = true;
1032 				highestTile = *pti;
1033 				highestSupportHeight = supportHeight;
1034 				highestSupportPlatform = i;
1035 			} else if (!highestTileFlag &&
1036 			           supportHeight <= lowestSupportHeight &&
1037 			           (ti->combinedTerrainMask() & (terrainSurface | terrainRaised))) {
1038 				lowestTileFlag = true;
1039 				lowestTile = *pti;
1040 				lowestSupportHeight = supportHeight;
1041 				lowestSupportPlatform = i;
1042 			}
1043 		}
1044 	}
1045 
1046 	if (highestTileFlag) {
1047 		if (ptiResult) *ptiResult = highestTile;
1048 		if (platformResult) *platformResult = highestSupportPlatform;
1049 		return highestSupportHeight;
1050 	}
1051 	if (lowestTileFlag) {
1052 		if (ptiResult) *ptiResult = lowestTile;
1053 		if (platformResult) *platformResult = lowestSupportPlatform;
1054 		return lowestSupportHeight;
1055 	}
1056 
1057 	if (ptiResult) {
1058 		ptiResult->surfaceTile = nullptr;
1059 		ptiResult->surfaceHeight = 0;
1060 	}
1061 	if (platformResult) *platformResult = 0;
1062 
1063 	return 0;
1064 }
1065 
1066 /* ===================================================================== *
1067    PathRequest Class
1068  * ===================================================================== */
1069 
1070 enum PathResult {
1071 	pathNotDone,
1072 	pathDone,
1073 	pathAborted
1074 };
1075 
1076 //  This if the base class for all PathRequests
1077 class PathRequest {
1078 	friend void addPathRequestToQueue(PathRequest *pr);
1079 
1080 protected:
1081 	Actor           *actor;                 // actor path applies to.
1082 	int16           smartness;              // how intelligent?
1083 	MotionTask      *mTask;                 // which motion task started this
1084 	uint8           flags;
1085 
1086 	enum pathFlags {
1087 		aborted     = (1 << 0),             // path request has been aborted
1088 
1089 		completed   = (1 << 1),             // pathfinder has found best path
1090 		run         = (1 << 2)
1091 	};
1092 
1093 	enum {
1094 		kPathSize = 16
1095 	};
1096 
1097 	//  These static members are initialized when the path request
1098 	//  becomes the current active request being serviced.
1099 	static TilePoint *path;
1100 	static int16            pathLength;
1101 
1102 	static StaticTilePoint  baseCoords,
1103 	       baseTileCoords,
1104 	       centerPt,       // The current center coordinates
1105 	       bestLoc;        // The best cell coordinates,
1106 	// currently visited
1107 
1108 	static uint8            centerPlatform,
1109 	       bestPlatform;
1110 
1111 	static int16            fetchRadius;
1112 
1113 	static int32            firstTick,
1114 	       lastTick,
1115 	       timeLimit;
1116 
1117 	static DirMaskGroup     *dirMasks;
1118 
1119 	//  Calculates the center point given the base coordinate of the
1120 	//  cell array and a queue item which contains cell coordinates.
calcCenterPt(const TilePoint & baseTileCoords_,const QueueItem & qi)1121 	static void calcCenterPt(const TilePoint &baseTileCoords_, const QueueItem &qi) {
1122 		centerPt.u = ((baseTileCoords_.u + qi.u) << kTileUVShift)
1123 		             +   kTileUVSize / 2;
1124 		centerPt.v = ((baseTileCoords_.v + qi.v) << kTileUVShift)
1125 		             +   kTileUVSize / 2;
1126 		centerPt.z = qi.z;
1127 
1128 		centerPlatform = qi.platform;
1129 	}
1130 
1131 	//  Constructor
1132 	PathRequest(Actor *a, int16 howSmart);
1133 
1134 public:
1135 	static PathTileRegion   *tileArray;
1136 
~PathRequest()1137 	virtual ~PathRequest() {
1138 		if (path)
1139 			delete[] path;
1140 
1141 		path = nullptr;
1142 	}
1143 
requestAbort(void)1144 	void requestAbort(void) {
1145 		flags |= aborted;
1146 	}
1147 
1148 	virtual void initialize(void);
1149 	virtual void finish(void);           // completion method
1150 	virtual void abortReq(void);                // abnormal termination method
1151 
1152 	PathResult findPath(void);
1153 
1154 	//  Set and evaluate a new center location.
1155 	virtual bool setCenter(
1156 	    const TilePoint &baseTileCoords,
1157 	    const QueueItem &qi) = 0;
1158 
1159 	//  Determine if path request will allow a move to the specified
1160 	//  location
1161 	virtual bool validMove(const TilePoint &testPt) = 0;
1162 
1163 	//  Virtual function for evaluating the cost of moving on stairs.
1164 	virtual int16 evaluateStairs(
1165 	    const TilePoint &testPt,
1166 	    Direction moveDir,
1167 	    Direction stairDir,
1168 	    int16 baseAltitute,
1169 	    int16 upperAltitude) = 0;
1170 
1171 	//  Virtual function for evaluating the cost of moving from the
1172 	//  current center location to the specified coordinates.
1173 	virtual int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform) = 0;
1174 
1175 	// NEW added by Evan 12/3
1176 	virtual bool timeLimitExceeded(void);
1177 
1178 };
1179 
1180 /* ===================================================================== *
1181    DestinationPathRequest Class
1182  * ===================================================================== */
1183 
1184 //  This class is used to request a path leading to a specified
1185 //  destination.
1186 class DestinationPathRequest : public PathRequest {
1187 protected:
1188 	TilePoint           destination;    // The destination of the path
1189 	uint8               destPlatform;   // The destination platform
1190 
1191 	//  These static members are initialized when the path request
1192 	//  becomes the current active request being serviced.
1193 	static StaticTilePoint targetCoords;   // The current destination coordinates
1194 	// quantized to the nearest tile
1195 	// center.
1196 	static uint8        targetPlatform;
1197 	static int16        bestDist,       // The distance from the target of
1198 	       // the best cell visited so far.
1199 	       centerCost;     // The distance from the target of
1200 	// the current center coordinates.
1201 public:
1202 	DestinationPathRequest(Actor *a, int16 howSmart);
1203 
1204 	//  Initialize the static data members for this path request.
1205 	void initialize(void);
1206 
1207 	//  Set and evaluate a new center location.
1208 	bool setCenter(
1209 	    const TilePoint &baseTileCoords,
1210 	    const QueueItem &qi);
1211 
1212 	bool validMove(const TilePoint &testPt);
1213 
1214 	//  Evaluate the cost of travelling on these stairs
1215 	int16 evaluateStairs(
1216 	    const TilePoint &testPt,
1217 	    Direction moveDir,
1218 	    Direction stairDir,
1219 	    int16 baseAltitude,
1220 	    int16 upperAltitude);
1221 
1222 	//  Evaluate the cost of the specified move
1223 	int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform);
1224 };
1225 
1226 /* ===================================================================== *
1227    WanderPathRequest Class
1228  * ===================================================================== */
1229 
1230 class WanderPathRequest : public PathRequest {
1231 protected:
1232 	bool                tethered;       //  Flag indicating if there is a
1233 	//  tether on this path
1234 
1235 	//  Tether coordinates
1236 	int16               tetherMinU,
1237 	                    tetherMinV,
1238 	                    tetherMaxU,
1239 	                    tetherMaxV;
1240 
1241 	//  These static members are initialized when the path request
1242 	//  becomes the current active request being serviced.
1243 	static StaticTilePoint startingCoords; // The actor's location at the
1244 	// beginning of the service.
1245 	static int16        bestDist,       // The distance from the target of
1246 	       // the best cell visited so far.
1247 	       centerCost;     // The distance from the target of
1248 	// the current center coordinates.
1249 
1250 public:
1251 	//  Constructor
1252 	WanderPathRequest(Actor *a, int16 howSmart);
1253 
1254 	//  Initialize the static data members
1255 	void initialize(void);
1256 
1257 	//  Set and evaluate a new center location.
1258 	bool setCenter(
1259 	    const TilePoint &baseTileCoords,
1260 	    const QueueItem &qi);
1261 
1262 	//  Determine if point is within the tether region if there is a
1263 	//  tether
1264 	bool validMove(const TilePoint &testPt);
1265 
1266 	//  Evaluate the cost of moving on the specified stairs.
1267 	int16 evaluateStairs(
1268 	    const TilePoint &testPt,
1269 	    Direction moveDir,
1270 	    Direction stairDir,
1271 	    int16 baseAltitude,
1272 	    int16 upperAltitude);
1273 
1274 	//  Evaluate the cost of moving from the current center location
1275 	//  to the specified location.
1276 	int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform);
1277 };
1278 
1279 /* ===================================================================== *
1280                             Globals
1281  * ===================================================================== */
1282 
1283 PathRequest                 *currentRequest = nullptr;
1284 
1285 static PathTilePosArray     *pathTileArray;
1286 static PathSubMetaFlags     subMetaFlags;
1287 
1288 static MaskComputer         *maskComp;
1289 
1290 static PriorityQueue<QueueItem, 192> *queue;
1291 static PathArray            *cellArray;
1292 
1293 static TileRegion           *objectVolumeArray;
1294 
1295 struct VolumeLookupNode {
1296 	VolumeLookupNode        *next;
1297 	TileRegion              *volume;
1298 };
1299 
1300 static VolumeLookupNode     volumeLookupNodePool[256];
1301 static VolumeLookupNode     *volumeLookupTable[searchDiameter][searchDiameter];
1302 
1303 TilePoint *PathRequest::path = nullptr;
1304 int16           PathRequest::pathLength;
1305 
1306 StaticTilePoint PathRequest::baseCoords = {0, 0, 0},
1307                 PathRequest::baseTileCoords = {0, 0, 0},
1308                 PathRequest::centerPt = {0, 0, 0},      // The current center coordinates
1309                 PathRequest::bestLoc = {0, 0, 0};       // The best cell coordinates,
1310 // currently visited
1311 uint8           PathRequest::centerPlatform,
1312                 PathRequest::bestPlatform;
1313 
1314 int16           PathRequest::fetchRadius;
1315 
1316 int32           PathRequest::firstTick,
1317                 PathRequest::timeLimit;
1318 
1319 DirMaskGroup    *PathRequest::dirMasks = nullptr;
1320 
1321 PathTileRegion  *PathRequest::tileArray = nullptr;
1322 
1323 StaticTilePoint DestinationPathRequest::targetCoords = {0, 0, 0};
1324 uint8           DestinationPathRequest::targetPlatform;
1325 int16           DestinationPathRequest::bestDist,
1326                 DestinationPathRequest::centerCost;
1327 
1328 StaticTilePoint WanderPathRequest::startingCoords = {0, 0, 0};
1329 int16           WanderPathRequest::bestDist,
1330                 WanderPathRequest::centerCost;
1331 
1332 /* ===================================================================== *
1333    PathQueue member functions
1334  * ===================================================================== */
1335 
push(const TilePoint & tp,uint8 platform,int cost,int direction,int8 platformDelta)1336 static void push(
1337     const   TilePoint &tp,
1338     uint8   platform,
1339     int     cost,
1340     int     direction,
1341     int8    platformDelta) {
1342 	assert(cellArray != nullptr);
1343 
1344 	PathCell        *cellPtr;
1345 	bool            newCell;
1346 	QueueItem       newItem;
1347 
1348 	//  Don't search beyond map edges
1349 	if (tp.u < 1 || tp.u >= searchDiameter - 1
1350 	        ||  tp.v < 1 || tp.v >= searchDiameter - 1)
1351 		return;
1352 
1353 	cellPtr = cellArray->makeCell(platform, tp.u, tp.v, &newCell);
1354 
1355 	assert(cellPtr != nullptr);
1356 
1357 	//  If the cell is already visited, only
1358 	//  update it if it was less cost to get here.
1359 	if (!newCell && cellPtr->cost <= cost) return;
1360 
1361 	newItem.u = tp.u;                       // coords of this point
1362 	newItem.v = tp.v;                       //
1363 	newItem.z = tp.z;
1364 	newItem.platform = platform;
1365 	newItem.cost = cost;
1366 	newItem.direction = direction;
1367 	newItem.pad = 0;
1368 
1369 	if (queue->insert(newItem)) {
1370 		cellPtr->direction = direction;
1371 		cellPtr->platformDelta = platformDelta;
1372 		cellPtr->cost = cost;
1373 		cellPtr->height = tp.z;
1374 	} else {
1375 		if (newCell)
1376 			cellArray->deleteCell(platform, tp.u, tp.v);
1377 	}
1378 }
1379 
1380 
1381 /* ===================================================================== *
1382    Member Functions
1383  * ===================================================================== */
1384 
1385 /* ===================================================================== *
1386    PathRequest Class
1387  * ===================================================================== */
1388 
PathRequest(Actor * a,int16 howSmart)1389 PathRequest::PathRequest(Actor *a, int16 howSmart) {
1390 	actor       = a;
1391 	smartness   = howSmart;
1392 	mTask       = actor->_moveTask;
1393 	flags       = mTask->flags & MotionTask::requestRun ? run : 0;
1394 
1395 	if (path == nullptr)
1396 		path = new TilePoint[kPathSize]();
1397 
1398 	mTask->pathFindTask = this;
1399 }
1400 
initialize(void)1401 void PathRequest::initialize(void) {
1402 	ProtoObj        *proto = actor->proto();
1403 	TilePoint       startingCoords = actor->getLocation();
1404 	int             uCoord, vCoord;
1405 	int             objectVolumes = 0;
1406 	int             nextAvailableLookupNode = 0;
1407 	int           curTileRegU,
1408 	              curTileRegV,
1409 	              minTileRegU,
1410 	              minTileRegV,
1411 	              maxTileRegU,
1412 	              maxTileRegV;
1413 	uint8 pCross = proto->crossSection;
1414 
1415 	firstTick = gameTime,
1416 	timeLimit = /*flags & run ? ticksPerSecond / 4 :*/ ticksPerSecond;
1417 
1418 	fetchRadius =
1419 	    ((kTileUVSize / 2 + pCross) >> kTileUVShift) + 1;
1420 
1421 	dirMasks = maskComp->computeMask(pCross);
1422 
1423 	//  Set the best location to the starting location
1424 	bestLoc.set(Nowhere.u, Nowhere.v, Nowhere.z);
1425 
1426 	//  Calculate where search cells will be projected onto map
1427 	baseTileCoords.u = (startingCoords.u >> kTileUVShift) - searchCenter;
1428 	baseTileCoords.v = (startingCoords.v >> kTileUVShift) - searchCenter;
1429 	baseTileCoords.z = 0;
1430 
1431 	baseCoords.u = baseTileCoords.u << kTileUVShift;
1432 	baseCoords.v = baseTileCoords.v << kTileUVShift;
1433 	baseCoords.z = 0;
1434 
1435 	//  Clear the priority queue
1436 	queue->clear();
1437 
1438 	//  Initialize the tile array
1439 	tileArray->init(
1440 	    actor->getMapNum(),
1441 	    TilePoint(
1442 	        baseTileCoords.u - 2,
1443 	        baseTileCoords.v - 2,
1444 	        0),
1445 	    TilePoint(
1446 	        searchDiameter + 4,
1447 	        searchDiameter + 4,
1448 	        0),
1449 	    (PathTilePosInfo *)pathTileArray,
1450 	    subMetaFlags);
1451 
1452 	for (uCoord = 0; uCoord < searchDiameter; uCoord++) {
1453 		for (vCoord = 0; vCoord < searchDiameter; vCoord++)
1454 			volumeLookupTable[uCoord][vCoord] = nullptr;
1455 	}
1456 
1457 	RegionalObjectIterator  iter(
1458 	    currentWorld,
1459 	    baseCoords,
1460 	    TilePoint(
1461 	        baseCoords.u
1462 	        + (searchCenter << kTileUVShift) * 2,
1463 	        baseCoords.v
1464 	        + (searchCenter << kTileUVShift) * 2,
1465 	        0));
1466 	GameObject *obj = nullptr;
1467 
1468 	for (iter.first(&obj);
1469 	        obj != nullptr;
1470 	        iter.next(&obj)) {
1471 		TilePoint       objLoc = obj->getLocation() - baseCoords;
1472 		ProtoObj        *objProto = obj->proto();
1473 		TileRegion      *objRegion = &objectVolumeArray[objectVolumes];
1474 		uint8 poCross = objProto->crossSection;
1475 
1476 		//  Obviously, we shouldn't block ourselves.
1477 		if (obj == actor || obj->isInvisible()) continue;
1478 
1479 		//  Dead corpses and invisible actors are not considered
1480 		//  obstacles.
1481 		if (isActor(obj)
1482 		        && (((Actor *)obj)->isDead()
1483 		            || ((Actor *)obj)->hasEffect(actorInvisible)))
1484 			continue;
1485 
1486 		//  Compute the objects volume
1487 		objRegion->min.u = objLoc.u - poCross;
1488 		objRegion->min.v = objLoc.v - poCross;
1489 		objRegion->min.z = objLoc.z;
1490 		objRegion->max.u = objLoc.u + poCross;
1491 		objRegion->max.v = objLoc.v + poCross;
1492 		objRegion->max.z = objLoc.z + objProto->height;
1493 
1494 		//  Compute the tile region which this object overlays
1495 		minTileRegU =   MAX(objRegion->min.u >> kTileUVShift, 0);
1496 		maxTileRegU =   MIN(
1497 		                    (objRegion->max.u + kTileUVMask) >> kTileUVShift,
1498 		                    searchDiameter);
1499 		minTileRegV =   MAX(objRegion->min.v >> kTileUVShift, 0);
1500 		maxTileRegV =   MIN(
1501 		                    (objRegion->max.v + kTileUVMask) >> kTileUVShift,
1502 		                    searchDiameter);
1503 
1504 		for (curTileRegU = minTileRegU;
1505 		        curTileRegU < maxTileRegU;
1506 		        curTileRegU++) {
1507 			assert(curTileRegU >= 0 && curTileRegU < searchDiameter);
1508 
1509 			for (curTileRegV = minTileRegV;
1510 			        curTileRegV < maxTileRegV;
1511 			        curTileRegV++) {
1512 				assert(curTileRegV >= 0 && curTileRegV < searchDiameter);
1513 
1514 				VolumeLookupNode    *node;
1515 				VolumeLookupNode    **tablePtrPtr;
1516 
1517 				//  Get the next lookup node
1518 				node = &volumeLookupNodePool[nextAvailableLookupNode++];
1519 				tablePtrPtr = &volumeLookupTable[curTileRegU][curTileRegV];
1520 
1521 				//  Link into lookup table
1522 				node->volume = objRegion;
1523 				node->next = *tablePtrPtr;
1524 				*tablePtrPtr = node;
1525 
1526 				if (nextAvailableLookupNode
1527 				        >=  ARRAYSIZE(volumeLookupNodePool))
1528 					goto big_break;
1529 			}
1530 		}
1531 
1532 		if (++objectVolumes >= kObjectVolumeArraySize) break;
1533 	}
1534 big_break:
1535 
1536 	//  Compute the actor's tile region
1537 	minTileRegU = (startingCoords.u - pCross) >> kTileUVShift;
1538 	minTileRegV = (startingCoords.v - pCross) >> kTileUVShift;
1539 	maxTileRegU = (startingCoords.u + pCross + kTileUVMask)
1540 	              >>  kTileUVShift;
1541 	maxTileRegV = (startingCoords.v + pCross + kTileUVMask)
1542 	              >>  kTileUVShift;
1543 
1544 	for (curTileRegU = minTileRegU;
1545 	        curTileRegU < maxTileRegU;
1546 	        curTileRegU++) {
1547 		for (curTileRegV = minTileRegV;
1548 		        curTileRegV < maxTileRegV;
1549 		        curTileRegV++) {
1550 			TilePoint       quantizedCoords,
1551 			                offsetVector;
1552 			uint8           platform;
1553 			int16           dist,
1554 			                zDist,
1555 			                cost;
1556 
1557 			//  Quantize this tile position to the tile center
1558 			quantizedCoords.u = (curTileRegU << kTileUVShift) + kTileUVSize / 2;
1559 			quantizedCoords.v = (curTileRegV << kTileUVShift) + kTileUVSize / 2;
1560 			quantizedCoords.z = startingCoords.z;
1561 			quantizedCoords.z = tileSlopeHeight(
1562 			                        quantizedCoords,
1563 			                        actor,
1564 			                        nullptr,
1565 			                        &platform);
1566 
1567 			//  If the height difference is too great skip this tile
1568 			//  position
1569 			if (ABS(quantizedCoords.z - startingCoords.z) > kMaxStepHeight)
1570 				continue;
1571 
1572 			//  Compute initial cost based upon the distance from the
1573 			//  starting location
1574 			offsetVector = quantizedCoords - startingCoords;
1575 			dist = offsetVector.quickHDistance();
1576 			zDist = ABS(offsetVector.z);
1577 			cost = dist + zDist;
1578 
1579 			//  Push this point
1580 			push(
1581 			    TilePoint(
1582 			        curTileRegU - baseTileCoords.u,
1583 			        curTileRegV - baseTileCoords.v,
1584 			        quantizedCoords.z),
1585 			    platform,
1586 			    cost,
1587 			    dirInvalid,
1588 			    0);
1589 		}
1590 	}
1591 }
1592 
finish(void)1593 void PathRequest::finish(void) {
1594 	Direction           prevDir;
1595 	int16               prevHeight = 0;
1596 	TilePoint           *resultSteps = path,
1597 	                    coords;
1598 	int16               stepCount = 0;
1599 	TilePoint           *res;
1600 	PathCell            *cell;
1601 
1602 	static TilePoint tempResult[32];
1603 
1604 	debugC(2, kDebugPath, "Finishing Path Request: %p", (void *)this);
1605 
1606 	if (bestLoc != Nowhere) {
1607 		cell = cellArray->getCell(bestPlatform, bestLoc.u, bestLoc.v);
1608 		assert(cell != nullptr);
1609 
1610 		if (cell->direction != dirInvalid) {
1611 			res = &tempResult[ARRAYSIZE(tempResult)];
1612 
1613 			prevDir = dirInvalid;
1614 
1615 			for (;;) {
1616 				int16       reverseDir;
1617 
1618 				cell = cellArray->getCell(bestPlatform, bestLoc.u, bestLoc.v);
1619 				assert(cell != nullptr);
1620 
1621 				if (cell->direction != dirInvalid) {
1622 					if (cell->direction != prevDir
1623 					        ||  ABS(cell->height - prevHeight) > kMaxStepHeight) {
1624 						if (res <= tempResult) break;
1625 
1626 						coords.u =
1627 						    (bestLoc.u << kTileUVShift)
1628 						    +   baseCoords.u
1629 						    +   kTileUVSize / 2;
1630 						coords.v =
1631 						    (bestLoc.v << kTileUVShift)
1632 						    +   baseCoords.v
1633 						    +   kTileUVSize / 2;
1634 						coords.z = cell->height;
1635 						*--res = coords;
1636 
1637 						prevDir = cell->direction;
1638 						prevHeight = cell->height;
1639 					}
1640 
1641 					reverseDir = (cell->direction + 4) & 0x07;
1642 					bestLoc += tDirTable2[reverseDir];
1643 					assert(bestLoc.u >= 0 && bestLoc.u < searchDiameter);
1644 					assert(bestLoc.v >= 0 && bestLoc.v < searchDiameter);
1645 					bestPlatform -= cell->platformDelta;
1646 					assert(bestPlatform < maxPlatforms);
1647 				} else
1648 					break;
1649 			}
1650 
1651 			if (resultSteps) {
1652 				while (stepCount < kPathSize
1653 				        &&  res < &tempResult[ARRAYSIZE(tempResult)]) {
1654 					*resultSteps++ = *res++;
1655 					stepCount++;
1656 				}
1657 			}
1658 		} else
1659 			//  if pathfinder didn't get anywhere, we're done
1660 			flags |= completed;
1661 	}
1662 
1663 	pathLength = stepCount;
1664 
1665 	if (mTask->pathFindTask == this && mTask->isWalk()) {
1666 		memcpy(mTask->pathList, path, pathLength * sizeof path[0]);
1667 		mTask->pathCount = pathLength;
1668 		mTask->pathIndex = 0;
1669 		mTask->flags |= MotionTask::reset;
1670 		if (flags & completed) mTask->flags |= MotionTask::finalPath;
1671 		mTask->pathFindTask = nullptr;
1672 	}
1673 }
1674 
abortReq(void)1675 void PathRequest::abortReq(void) {
1676 	debugC(4, kDebugPath, "Aborting Path Request: %p", (void *)this);
1677 
1678 	if (mTask->pathFindTask == this)
1679 		mTask->pathFindTask = nullptr;
1680 }
1681 
1682 
findPath(void)1683 PathResult PathRequest::findPath(void) {
1684 	assert(cellArray != nullptr);
1685 
1686 	static const uint8 costTable[] = {4, 10, 12, 16, 12, 10, 4, 0, 4, 10, 12, 16, 12, 10, 4, 0};
1687 
1688 	ProtoObj        *proto = actor->proto();
1689 	QueueItem       qi;
1690 	uint8 pCross = proto->crossSection;
1691 
1692 	debugC(4, kDebugPath, "Finding Path for %p: pCross = %d", (void *)this, pCross);
1693 
1694 	if (flags & aborted) return pathAborted;
1695 
1696 	int32 lastTick_ = gameTime;
1697 
1698 	while (queue->remove(qi)) {
1699 		assert(cellArray->getCell(qi.platform, qi.u, qi.v) != nullptr);
1700 		assert(qi.u >= 1 && qi.u < searchDiameter - 1);
1701 		assert(qi.v >= 1 && qi.v < searchDiameter - 1);
1702 
1703 		TilePoint   centerTileCoords;
1704 		StaticTilePoint *tDir;
1705 		int32       i,
1706 		            dir,
1707 		            endDir;
1708 
1709 
1710 		//  Limit the total path length to avoid maze-solving
1711 		if (qi.cost > smartness) continue;
1712 
1713 		//  Set a new center and determine if we're done
1714 		if (setCenter(baseTileCoords, qi)) return pathDone;
1715 
1716 		//  Calculate the coordinates of the center in tiles
1717 		centerTileCoords.u = qi.u + baseTileCoords.u;
1718 		centerTileCoords.v = qi.v + baseTileCoords.v;
1719 		centerTileCoords.z = 0;
1720 
1721 		if (qi.direction == dirInvalid) {
1722 			//  If this is the starting position, check all directions
1723 			i = dir = 0;
1724 			tDir = tDirTable2;
1725 			endDir = 8;
1726 
1727 			tileArray->fetchTileSection(
1728 			    TilePoint(qi.u - fetchRadius, qi.v - fetchRadius, 0)
1729 			    +   baseTileCoords,
1730 			    TilePoint(
1731 			        (fetchRadius << 1) + 1,
1732 			        (fetchRadius << 1) + 1,
1733 			        0));
1734 		} else {
1735 			//  Check only the forward directions
1736 			i = dir = (qi.direction + 6) & 0x7;
1737 			tDir = &tDirTable2[dir];
1738 			endDir = i + 5;
1739 
1740 			switch (qi.direction) {
1741 			case 0:
1742 				tileArray->fetchTileSection(
1743 				    TilePoint(
1744 				        qi.u + fetchRadius,
1745 				        qi.v - fetchRadius,
1746 				        0)
1747 				    +   baseTileCoords,
1748 				    TilePoint(1, fetchRadius << 1, 0));
1749 					// fall through
1750 			case 1:
1751 				tileArray->fetchTileSection(
1752 				    TilePoint(
1753 				        qi.u - fetchRadius,
1754 				        qi.v + fetchRadius,
1755 				        0)
1756 				    +   baseTileCoords,
1757 				    TilePoint((fetchRadius << 1) + 1, 1, 0));
1758 				break;
1759 			case 2:
1760 				tileArray->fetchTileSection(
1761 				    TilePoint(
1762 				        qi.u - fetchRadius + 1,
1763 				        qi.v + fetchRadius,
1764 				        0)
1765 				    +   baseTileCoords,
1766 				    TilePoint(fetchRadius << 1, 1, 0));
1767 					// fall through
1768 			case 3:
1769 				tileArray->fetchTileSection(
1770 				    TilePoint(
1771 				        qi.u - fetchRadius,
1772 				        qi.v - fetchRadius,
1773 				        0)
1774 				    +   baseTileCoords,
1775 				    TilePoint(1, (fetchRadius << 1) + 1, 0));
1776 				break;
1777 			case 4:
1778 				tileArray->fetchTileSection(
1779 				    TilePoint(
1780 				        qi.u - fetchRadius,
1781 				        qi.v - fetchRadius + 1,
1782 				        0)
1783 				    +   baseTileCoords,
1784 				    TilePoint(1, fetchRadius << 1, 0));
1785 					// fall through
1786 			case 5:
1787 				tileArray->fetchTileSection(
1788 				    TilePoint(
1789 				        qi.u - fetchRadius,
1790 				        qi.v - fetchRadius,
1791 				        0)
1792 				    +   baseTileCoords,
1793 				    TilePoint((fetchRadius << 1) + 1, 1, 0));
1794 				break;
1795 			case 6:
1796 				tileArray->fetchTileSection(
1797 				    TilePoint(
1798 				        qi.u - fetchRadius,
1799 				        qi.v - fetchRadius,
1800 				        0)
1801 				    +   baseTileCoords,
1802 				    TilePoint(fetchRadius << 1, 1,  0));
1803 					// fall through
1804 			case 7:
1805 				tileArray->fetchTileSection(
1806 				    TilePoint(
1807 				        qi.u + fetchRadius,
1808 				        qi.v - fetchRadius,
1809 				        0)
1810 				    +   baseTileCoords,
1811 				    TilePoint(1, (fetchRadius << 1) + 1, 0));
1812 			}
1813 
1814 		}
1815 
1816 		for (;
1817 		        i < endDir;
1818 		        tDir = &tDirTable2[(dir = (++i & 0x7))]) {
1819 			if (!validMove(centerPt + tDirTable3[dir]))
1820 				continue;
1821 
1822 			PathTileInfo    pti;
1823 			TilePoint       testPt = centerPt;
1824 			uint8           testPlatform;
1825 			uint32          terrain = 0;
1826 			int32           cost;
1827 			DirMask         &dMask = (*dirMasks)[dir];
1828 			int16           prevZ = centerPt.z;
1829 
1830 			for (int d = 0; d < 4; d++) {
1831 				int       u, v;
1832 				uint8       maskU, maskV;
1833 				PointMask   &ptMask = dMask[d];
1834 				TileRegion  maskReg,
1835 				            actorVolume;
1836 
1837 				maskReg.min.u = centerTileCoords.u + ptMask.offset.u;
1838 				maskReg.max.u = maskReg.min.u + ptMask.size.u;
1839 				maskReg.min.v = centerTileCoords.v + ptMask.offset.v;
1840 				maskReg.max.v = maskReg.min.v + ptMask.size.v;
1841 
1842 				testPt.u += tDirTable[dir].u;
1843 				testPt.v += tDirTable[dir].v;
1844 				testPt.z =  tileSlopeHeight(
1845 				                *tileArray,
1846 				                testPt,
1847 				                actor,
1848 				                &pti,
1849 				                &testPlatform);
1850 
1851 				//  Determine if elevation change is too great
1852 				if (ABS(testPt.z - prevZ) <= kMaxStepHeight)
1853 					prevZ = testPt.z;
1854 				else
1855 					goto big_continue;
1856 
1857 				//  Compute the actor's volume at this point relative to
1858 				//  the base of the search region
1859 				actorVolume.min.u =     testPt.u
1860 				                        -   baseCoords.u
1861 				                        -   pCross;
1862 				actorVolume.max.u = actorVolume.min.u + pCross * 2;
1863 				actorVolume.min.v =     testPt.v
1864 				                        -   baseCoords.v
1865 				                        -   pCross;
1866 				actorVolume.max.v = actorVolume.min.v + pCross * 2;
1867 				actorVolume.min.z = testPt.z;
1868 				actorVolume.max.z = testPt.z + proto->height;
1869 				int16 aph = actor->proto()->height;
1870 
1871 				for (u = maskReg.min.u, maskU = 0;
1872 				        u < maskReg.max.u;
1873 				        u++, maskU++) {
1874 					PathTilePosInfo *arrRow =
1875 					    &tileArray->array[
1876 					        (u - tileArray->origin.u)
1877 					        *   tileArray->area.v];
1878 
1879 					for (v = maskReg.min.v, maskV = 0;
1880 					        v < maskReg.max.v;
1881 					        v++, maskV++) {
1882 						VolumeLookupNode    *node;
1883 
1884 						//  Lookup any potentially intersecting object
1885 						//  volumes
1886 						for (node = volumeLookupTable
1887 						            [u - baseTileCoords.u]
1888 						            [v - baseTileCoords.v];
1889 						        node != nullptr;
1890 						        node = node->next) {
1891 							TileRegion *trv = node->volume;
1892 							//  Check for volume intersection
1893 							if (trv->min.u < actorVolume.max.u
1894 							        &&  actorVolume.min.u < trv->max.u
1895 							        &&  trv->min.v < actorVolume.max.v
1896 							        &&  actorVolume.min.v < trv->max.v
1897 							        &&  trv->min.z < actorVolume.max.z
1898 							        &&      actorVolume.min.z + kMaxStepHeight
1899 							        <   trv->max.z)
1900 								goto big_continue;
1901 						}
1902 
1903 						terrain |=  tileTerrain(
1904 						                &arrRow[v - tileArray->origin.v],
1905 						                ptMask.mask[(maskU << 2) | maskV],
1906 						                testPt.z,
1907 						                testPt.z + aph);
1908 					}
1909 				}
1910 			}
1911 
1912 			if (terrain & (terrainImpassable | terrainRaised)) continue;
1913 
1914 			//  determine the height of the center of the tile
1915 			//  that we're testing.
1916 
1917 			//  Assign costs based on the direction of travel
1918 
1919 			if (terrain & terrainSlow) {
1920 				cost = dir & 1 ? straightHardCost : diagHardCost;
1921 			} else if (terrain & terrainAverage) {
1922 				cost = dir & 1 ? straightNormalCost : diagNormalCost;
1923 			} else {
1924 				cost = dir & 1 ? straightEasyCost : diagEasyCost;
1925 			}
1926 
1927 			//  We must treat stairs as a special case
1928 
1929 			if (pti.surfaceTile != nullptr
1930 			        && (pti.surfaceTile->combinedTerrainMask() & terrainStair)) {
1931 				uint8   *cornerHeight = pti.surfaceTile->attrs.cornerHeight;
1932 				uint8   stairDir;
1933 				int16   stairHeight;
1934 
1935 				//  Determine the direction and upper altitude of the
1936 				//  stairs
1937 
1938 				if (*((uint16 *)&cornerHeight[0]) == 0) {
1939 					stairDir = 1;
1940 					stairHeight = pti.surfaceHeight + cornerHeight[2];
1941 				} else if (*((uint16 *)&cornerHeight[1]) == 0) {
1942 					stairDir = 3;
1943 					stairHeight = pti.surfaceHeight + cornerHeight[3];
1944 				} else if (*((uint16 *)&cornerHeight[2]) == 0) {
1945 					stairDir = 5;
1946 					stairHeight = pti.surfaceHeight + cornerHeight[0];
1947 				} else if (cornerHeight[0] == 0 && cornerHeight[3] == 0) {
1948 					stairDir = 7;
1949 					stairHeight = pti.surfaceHeight + cornerHeight[1];
1950 				} else
1951 					continue;
1952 
1953 				//  Do not go onto the stair at a right angle
1954 
1955 				if (stairDir == 1 || stairDir == 5) {
1956 					if (dir == 3 || dir == 7)
1957 						continue;
1958 				} else if (stairDir == 3 || stairDir == 7) {
1959 					if (dir == 1 || dir == 5)
1960 						continue;
1961 				}
1962 
1963 				//  Add any additional costs for travelling on these
1964 				//  stairs.
1965 				cost += evaluateStairs(
1966 				            testPt,
1967 				            dir,
1968 				            stairDir,
1969 				            pti.surfaceHeight,
1970 				            stairHeight);
1971 
1972 			}
1973 
1974 			//  Assign additional costs based on having to travel
1975 			//  uphill or having to jump down steep slopes
1976 			else if (testPt.z > centerPt.z
1977 			         ||  testPt.z < centerPt.z - 12) {
1978 				cost += ((testPt.z - centerPt.z)
1979 				         * (testPt.z - centerPt.z)) >> 5;
1980 			}
1981 
1982 			//  If the drop-off is too much, then don't go there!
1983 			//  (i.e. don't jmup off of cliffs). Also we can
1984 			//  only climb steps below a certain height.
1985 
1986 //			if (   testPt.z < centerPt.z - kMaxJumpStep
1987 //				||    testPt.z > centerPt.z + kMaxStepHeight)
1988 //					continue;
1989 
1990 			//  Turns are expensive, the sharper turns are more so.
1991 
1992 			cost += costTable[
1993 			            7
1994 			            +   dir
1995 			            - (qi.direction != dirInvalid
1996 			               ?   qi.direction
1997 			               :   actor->_currentFacing)];
1998 
1999 #if VISUAL1
2000 			TPLine(centerPt, testPt);
2001 #endif
2002 			//  Determine the final cost of moving to this cell
2003 			cost = clamp(0L,
2004 			             (int32)qi.cost
2005 			             + (int32)cost
2006 			             + (int32)evaluateMove(testPt, testPlatform),
2007 			             (int32)maxint16);
2008 
2009 			//  Cost should never be less than previous cost
2010 			cost = MAX<int16>(cost, qi.cost);
2011 
2012 			//  Push the new point onto the queue->
2013 
2014 			push(
2015 			    TilePoint(
2016 			        qi.u + tDir->u,
2017 			        qi.v + tDir->v,
2018 			        testPt.z),
2019 			    testPlatform,
2020 			    cost,
2021 			    dir,
2022 			    testPlatform - centerPlatform);
2023 			assert(cellArray->getCell(centerPlatform, qi.u, qi.v) != nullptr);
2024 
2025 big_continue:
2026 			;
2027 		}
2028 
2029 		if ((gameTime - lastTick_) >= 4) {         // JEFFKLUDGE
2030 			if (timeLimitExceeded())
2031 				return pathDone;
2032 		}
2033 	}
2034 
2035 	return pathDone;
2036 }
2037 
2038 //-------------------------------------------------------------------
2039 // Path finder time management:
2040 //   Originally all requests had a time limit of 72 and IQ of 400
2041 //   I have rewired it to use an IQ of 400 for player actors, and
2042 //   100 for NPCs. That IQ also affects the time limit now. Players
2043 //   will generally get (72/2)+(400/10) or 76 ticks. NPCs will
2044 //   usually get (72/2)+(100/10) or 46 ticks.
2045 
2046 
timeLimitExceeded(void)2047 bool PathRequest::timeLimitExceeded(void) {
2048 #ifdef OLD_PATHFINDER_TIME_MGMT
2049 	return (gameTime - firstTick >= timeLimit);
2050 #else
2051 	int32 cutoff = smartness / (queue->getCount() ? 5 : 8);
2052 	return (gameTime - firstTick >= cutoff);
2053 #endif
2054 }
2055 
2056 
2057 /* ===================================================================== *
2058    DestinationPathRequest Class
2059  * ===================================================================== */
2060 
DestinationPathRequest(Actor * a,int16 howSmart)2061 DestinationPathRequest::DestinationPathRequest(Actor *a, int16 howSmart) :
2062 	PathRequest(a, howSmart) {
2063 	//  Quantize the target destination to the nearest tile center.
2064 	mTask->finalTarget.u = (mTask->finalTarget.u & ~kTileUVMask) + kTileUVSize / 2;
2065 	mTask->finalTarget.v = (mTask->finalTarget.v & ~kTileUVMask) + kTileUVSize / 2;
2066 	mTask->finalTarget.z =  tileSlopeHeight(
2067 	                            mTask->finalTarget,
2068 	                            a,
2069 	                            nullptr,
2070 	                            &destPlatform);
2071 
2072 	destination = mTask->finalTarget;
2073 }
2074 
2075 //  Initialize the static data members
initialize(void)2076 void DestinationPathRequest::initialize(void) {
2077 	debugC(2, kDebugPath, "Initializing Path Request: %p", (void *)this);
2078 
2079 	PathRequest::initialize();
2080 
2081 	//  Initialize bestDist to the highest possible value.
2082 	bestDist = maxint16;
2083 
2084 	//  Quantize the target coordinates to the nearest tile center.
2085 	targetCoords.u = (destination.u & ~kTileUVMask) + kTileUVSize / 2;
2086 	targetCoords.v = (destination.v & ~kTileUVMask) + kTileUVSize / 2;
2087 	targetCoords.z = destination.z;
2088 	targetPlatform = destPlatform;
2089 }
2090 
2091 //  Set and evaluate a new center location.
setCenter(const TilePoint & baseTileCoords_,const QueueItem & qi)2092 bool DestinationPathRequest::setCenter(
2093     const TilePoint &baseTileCoords_,
2094     const QueueItem &qi) {
2095 	int16       dist,
2096 	            zDist,
2097 	            platDiff;
2098 	TilePoint   targetDelta;
2099 
2100 	//  Calculate the center coordinates.
2101 	calcCenterPt(baseTileCoords_, qi);
2102 
2103 	//  Determine the target vector in order to calculate distance.
2104 	targetDelta = (targetCoords - centerPt);
2105 	dist = targetDelta.quickHDistance();
2106 	zDist = ABS(targetDelta.z);
2107 	platDiff = ABS(centerPlatform - targetPlatform);
2108 	centerCost = dist + zDist * (platDiff + 1);
2109 
2110 	//  Determine if this location is closer than any location we have
2111 	//  previously visited.
2112 	if (centerCost < bestDist) {
2113 		//  Save closest point encountered.
2114 
2115 		bestLoc.u = qi.u;
2116 		bestLoc.v = qi.v;
2117 		bestLoc.z = qi.z;
2118 		bestPlatform = qi.platform;
2119 		bestDist = centerCost;
2120 
2121 		//  If we're at target square, then we're done!
2122 		if (dist == 0 && zDist <= kMaxStepHeight) {
2123 			flags |= PathRequest::completed;
2124 
2125 			//  Return true to indicate that the path finding is done.
2126 			return true;
2127 		}
2128 	}
2129 
2130 	return false;
2131 }
2132 
validMove(const TilePoint &)2133 bool DestinationPathRequest::validMove(const TilePoint &) {
2134 	return true;
2135 }
2136 
2137 //  Evaluate the cost of moving on the specified stairs in the specified
2138 //  direction
evaluateStairs(const TilePoint & testPt,Direction moveDir,Direction stairDir,int16 baseAltitude,int16 upperAltitude)2139 int16 DestinationPathRequest::evaluateStairs(
2140     const TilePoint &testPt,    //  Coordinates of location we are testing
2141     Direction moveDir,          //  Direction of movement
2142     Direction stairDir,         //  Direction of up stairs
2143     int16 baseAltitude,         //  Altitude of bottom of the stairs
2144     int16 upperAltitude) {      //  Altitude of the top of the stairs
2145 	int16   cost = 0;
2146 
2147 	//  Determine if the stairs are going towards the
2148 	//  altitude of the target coordinates.  If not, assign
2149 	//  additional costs.
2150 
2151 	if (targetCoords.z >= upperAltitude) {
2152 		if (moveDir != stairDir) {
2153 			cost = ((testPt.z - centerPt.z)
2154 			        * (testPt.z - centerPt.z)) >> 4;
2155 		}
2156 	} else if (targetCoords.z <= baseAltitude) {
2157 		if (moveDir == stairDir) {
2158 			cost = ((testPt.z - centerPt.z)
2159 			        * (testPt.z - centerPt.z)) >> 4;
2160 		}
2161 	}
2162 
2163 	return cost;
2164 }
2165 
2166 //  Evaluate the cost of moving to the specified point from the
2167 //  current center location.
evaluateMove(const TilePoint & testPt,uint8 testPlatform)2168 int16 DestinationPathRequest::evaluateMove(
2169     const TilePoint &testPt,
2170     uint8           testPlatform) {
2171 	int16       dist,
2172 	            zDist,
2173 	            platDiff;
2174 	TilePoint   targetDelta;
2175 
2176 	//  Determine the target vector of the specified coordinates, in
2177 	//  order to calculate the distance.
2178 	targetDelta = targetCoords - testPt;
2179 	dist = targetDelta.quickHDistance();
2180 	zDist = ABS(targetDelta.z);
2181 	platDiff = ABS(testPlatform - targetPlatform);
2182 
2183 	return (dist + zDist * (platDiff + 1) - centerCost) >> 2;
2184 }
2185 
2186 /* ===================================================================== *
2187    WanderPathRequest Class
2188  * ===================================================================== */
2189 
WanderPathRequest(Actor * a,int16 howSmart)2190 WanderPathRequest::WanderPathRequest(
2191     Actor *a,
2192     int16 howSmart) :
2193 	PathRequest(a, howSmart) {
2194 	if (mTask->flags & MotionTask::tethered) {
2195 		tethered = true;
2196 		tetherMinU = mTask->tetherMinU;
2197 		tetherMinV = mTask->tetherMinV;
2198 		tetherMaxU = mTask->tetherMaxU;
2199 		tetherMaxV = mTask->tetherMaxV;
2200 	} else {
2201 		tethered = false;
2202 		tetherMinU = tetherMinV = tetherMaxU = tetherMaxV = 0;
2203 	}
2204 }
2205 
2206 //  Initialize the static data members
initialize(void)2207 void WanderPathRequest::initialize(void) {
2208 	PathRequest::initialize();
2209 
2210 	//  Initialize bestDist to zero.
2211 	bestDist = 0;
2212 	startingCoords.set(actor->getLocation().u,
2213 	                   actor->getLocation().v,
2214 	                   actor->getLocation().z);
2215 }
2216 
2217 
2218 //  Set and evaluate a new center location.
setCenter(const TilePoint & baseTileCoords_,const QueueItem & qi)2219 bool WanderPathRequest::setCenter(
2220     const TilePoint &baseTileCoords_,
2221     const QueueItem &qi) {
2222 	int16       dist,
2223 	            zDist;
2224 	TilePoint   movementDelta;
2225 
2226 	//  Calculate the center coordinates.
2227 	calcCenterPt(baseTileCoords_, qi);
2228 
2229 	//  Determine the movement vector in order to calculate distance.
2230 	movementDelta = (startingCoords - centerPt);
2231 	dist = movementDelta.quickHDistance();
2232 	zDist = ABS(movementDelta.z);
2233 	centerCost = dist + zDist;
2234 
2235 	//  Determine if this location is farther than any location we have
2236 	//  previously visited.
2237 	if (centerCost > bestDist) {
2238 		//  Save farthest point encountered.
2239 
2240 		bestLoc.u = qi.u;
2241 		bestLoc.v = qi.v;
2242 		bestLoc.z = qi.z;
2243 		bestPlatform = qi.platform;
2244 		bestDist = centerCost;
2245 	}
2246 
2247 	return false;
2248 }
2249 
validMove(const TilePoint & testPt)2250 bool WanderPathRequest::validMove(const TilePoint &testPt) {
2251 	return      !tethered
2252 	            || (testPt.u >= tetherMinU
2253 	                &&  testPt.u < tetherMaxU
2254 	                &&  testPt.v >= tetherMinV
2255 	                &&  testPt.v < tetherMaxV);
2256 }
2257 
2258 //  There will be no additional costs for travelling on stairs
evaluateStairs(const TilePoint &,Direction,Direction,int16,int16)2259 int16 WanderPathRequest::evaluateStairs(
2260     const TilePoint &,
2261     Direction,
2262     Direction,
2263     int16,
2264     int16) {
2265 	return 0;
2266 }
2267 
2268 //  Evaluate the cost of moving to the specified point from the
2269 //  current center location.
evaluateMove(const TilePoint & testPt,uint8)2270 int16 WanderPathRequest::evaluateMove(const TilePoint &testPt, uint8) {
2271 	int16       dist,
2272 	            zDist;
2273 	TilePoint   movementDelta;
2274 
2275 	//  Determine the movement vector of the specified coordinates, in
2276 	//  order to calculate the distance.
2277 	movementDelta = startingCoords - testPt;
2278 	dist = movementDelta.quickHDistance();
2279 	zDist = ABS(movementDelta.z) >> 1;
2280 
2281 	return (centerCost - (dist + zDist)) >> 1;
2282 }
2283 
runPathFinder(void)2284 void runPathFinder(void) {
2285 	if (currentRequest == nullptr && !g_vm->_pathQueue.empty()) {
2286 		currentRequest = g_vm->_pathQueue.front();
2287 		g_vm->_pathQueue.pop_front();
2288 		currentRequest->initialize();
2289 	}
2290 
2291 	if (currentRequest != nullptr) {
2292 		PathResult  result;
2293 
2294 		result = currentRequest->findPath();
2295 
2296 		if (result != pathNotDone) {
2297 			if (result == pathDone)
2298 				currentRequest->finish();
2299 			else
2300 				currentRequest->abortReq();
2301 
2302 			delete currentRequest;
2303 			currentRequest = nullptr;
2304 
2305 			cellArray->reset();
2306 		}
2307 	}
2308 }
2309 
addPathRequestToQueue(PathRequest * pr)2310 void addPathRequestToQueue(PathRequest *pr) {
2311 	Actor           *a = pr->actor;
2312 	Actor           *centerActor = getCenterActor();
2313 
2314 	if (a == centerActor)
2315 		g_vm->_pathQueue.push_front(pr);
2316 	else {
2317 		if (isPlayerActor(a)) {
2318 			Common::List<PathRequest *>::iterator it;
2319 
2320 			for (it = g_vm->_pathQueue.begin(); it != g_vm->_pathQueue.end(); ++it) {
2321 				Actor       *prActor = (*it)->actor;
2322 
2323 				if (prActor != centerActor || !isPlayerActor(prActor))
2324 					break;
2325 			}
2326 
2327 			if (it != g_vm->_pathQueue.end())
2328 				g_vm->_pathQueue.insert(it, pr);
2329 			else
2330 				g_vm->_pathQueue.push_back(pr);
2331 		} else
2332 			g_vm->_pathQueue.push_back(pr);
2333 	}
2334 }
2335 
RequestPath(MotionTask * mTask,int16 smartness)2336 void RequestPath(MotionTask *mTask, int16 smartness) {
2337 	DestinationPathRequest      *pr;
2338 	Actor                       *a = (Actor *)mTask->object;
2339 
2340 	if ((pr = new DestinationPathRequest(a, smartness)) != nullptr)
2341 		addPathRequestToQueue(pr);
2342 }
2343 
RequestWanderPath(MotionTask * mTask,int16 smartness)2344 void RequestWanderPath(MotionTask *mTask, int16 smartness) {
2345 	WanderPathRequest           *pr;
2346 	Actor                       *a = (Actor *)mTask->object;
2347 
2348 	if ((pr = new WanderPathRequest(a, smartness)) != nullptr)
2349 		addPathRequestToQueue(pr);
2350 }
2351 
abortPathFind(MotionTask * mTask)2352 void abortPathFind(MotionTask *mTask) {
2353 	if (mTask->pathFindTask) {
2354 		PathRequest     *pr = mTask->pathFindTask;
2355 
2356 		if (pr == currentRequest)
2357 			pr->requestAbort();
2358 		else
2359 			g_vm->_pathQueue.remove(pr);
2360 
2361 		mTask->pathFindTask = nullptr;
2362 	}
2363 }
2364 
2365 /* ===================================================================== *
2366    A special pathfinder to select a random site reachable from another one.
2367  * ===================================================================== */
2368 
2369 /*  Specs:
2370 
2371     1.  Find a locations which is reachable from 'center', and which
2372         is between minDist and maxDist tiles away (preferably midway
2373         between them).
2374     2.  Terrain cost and direction of travel need not be considered.
2375     3.  Don't put creatures on top of houses, but do put them
2376         on top of hills.
2377     4.  To simplify even further, checking in diagonal directions
2378         is not needed.
2379     5.  This routine should probably run synchronously, and therefore
2380         cannot share the data structures of the real pathfinder.
2381 */
2382 
2383 enum cellStates {
2384 	cellUnvisited = 0,
2385 	cellOccupied = (1 << 0),
2386 	cellVisited = (1 << 1)
2387 };
2388 
2389 typedef uint8       SimpleCellArray[searchDiameter][searchDiameter];
2390 
2391 static PriorityQueue<QueueItem, 128> *squeue;
2392 
spush(const TilePoint & tp,int cost,int direction)2393 static void spush(const TilePoint &tp, int cost, int direction) {
2394 	QueueItem       newItem;
2395 
2396 	//  Don't search beyond map edges
2397 
2398 	if (tp.u < 1 || tp.u >= searchDiameter - 1
2399 	        ||  tp.v < 1 || tp.v >= searchDiameter - 1)
2400 		return;
2401 
2402 	newItem.u = tp.u;                       // coords of this point
2403 	newItem.v = tp.v;                       //
2404 	newItem.z = tp.z;
2405 	newItem.cost = cost;
2406 	newItem.direction = direction;
2407 	newItem.platform = 0;
2408 
2409 	squeue->insert(newItem);
2410 }
2411 
2412 /*  ordering of bits:
2413 
2414     u3v3 u3v2 u3v1 u3v0                 +U
2415     u2v3 u2v2 u2v1 u2v0    +V <---> -V   |
2416     u1v3 u1v2 u1v1 u1v0                  |
2417     u0v3 u0v2 u0v1 u0v0                 -U
2418 
2419      15      14      13      12      11      10      09      08
2420     ----    ----    ----    ----    ----    ----    ----    ----
2421     u3v3    u3v2    u3v1    u3v0    u2v3    u2v2    u2v1    u2v0
2422 
2423      07      06      05      04      03      02      01      00
2424     ----    ----    ----    ----    ----    ----    ----    ----
2425     u1v3    u1v2    u1v1    u1v0    u0v3    u0v2    u0v1    u0v0
2426 */
2427 
2428 /*  0000 0110 0000 0000 <--transition masks
2429     0110 0110 0111 1110
2430     0110 0110 0111 1110
2431     0110 0000 0000 0000
2432      -U   +U   -V   +V
2433 
2434     0666 6660 0770 0ee0
2435 */
2436 
2437 static const uint16
2438 posUMask = 0x6660,
2439 negUMask = 0x0666,
2440 posVMask = 0x0770,
2441 negVMask = 0x0ee0;
2442 
2443 uint16 sTerrainMasks[8] = {
2444 	posUMask, negUMask,             //  dirUpLeft (U+)
2445 	negVMask, posVMask,             //  dirDownLeft (V-)
2446 	negUMask, posUMask,             //  dirDownRight (U-)
2447 	posVMask, negVMask,             //  dirUpRight (V+)
2448 };
2449 
selectNearbySite(ObjectID worldID,const TilePoint & startingCoords,int32 minDist,int32 maxDist,bool offScreenOnly)2450 TilePoint selectNearbySite(
2451     ObjectID        worldID,
2452     const TilePoint &startingCoords,
2453     int32           minDist,
2454     int32           maxDist,
2455     bool            offScreenOnly) {        // true if we want it off-screen
2456 	assert(isWorld(worldID));
2457 
2458 	TilePoint       baseCoords,
2459 	                baseTileCoords,
2460 	                centerPt,               // The current center coordinates
2461 	                bestLoc;                // The best cell coordinates,
2462 	// currently visited
2463 	int16           mapNum = GameWorld::IDtoMapNum(worldID);
2464 
2465 	int32           bestRating = -100,
2466 	                bestPossible = (maxDist - minDist) / 2;
2467 
2468 	QueueItem       qi;
2469 
2470 	//  Allocate the array of cells
2471 	SimpleCellArray *cellArray1 = (SimpleCellArray *)malloc(sizeof * cellArray1);
2472 
2473 	//  Nowhere indicates failure of the algorithm.
2474 	bestLoc = Nowhere;
2475 
2476 	//  Calculate where search cells will be projected onto map
2477 	baseTileCoords.u = (startingCoords.u >> kTileUVShift) - searchCenter;
2478 	baseTileCoords.v = (startingCoords.v >> kTileUVShift) - searchCenter;
2479 	baseTileCoords.z = 0;
2480 
2481 	baseCoords.u = baseTileCoords.u << kTileUVShift;
2482 	baseCoords.v = baseTileCoords.v << kTileUVShift;
2483 	baseCoords.z = 0;
2484 
2485 	//  Clear the search array and the queue
2486 	memset(cellArray1, cellUnvisited, sizeof(*cellArray1));
2487 	squeue->clear();
2488 
2489 	//  Iterate through all actors in the region and mark areas
2490 	//  as occupied.
2491 	RegionalObjectIterator  iter(
2492 	    (GameWorld *)GameObject::objectAddress(worldID),
2493 	    baseCoords,
2494 	    TilePoint(
2495 	        baseCoords.u
2496 	        + (searchCenter << kTileUVShift) * 2,
2497 	        baseCoords.v
2498 	        + (searchCenter << kTileUVShift) * 2,
2499 	        0));
2500 	GameObject *obj = nullptr;
2501 
2502 	for (iter.first(&obj);
2503 	        obj != nullptr;
2504 	        iter.next(&obj)) {
2505 		TilePoint       objLoc = obj->getLocation();
2506 		ProtoObj        *objProto = obj->proto();
2507 
2508 		//  If the object is higher than the actor's head, or
2509 		//  low enough to step over, then ignore it.
2510 		if (objLoc.z >= startingCoords.z + 80
2511 		        ||  objLoc.z + objProto->height <= startingCoords.z + kMaxStepHeight / 2) {
2512 			continue;
2513 		}
2514 
2515 		//  Calculate which tile actor is standing on.
2516 		objLoc = (objLoc - baseCoords) >> kTileUVShift;
2517 
2518 		//  If that tile is in the search area, then mark it.
2519 		if (objLoc.u >= 0 && objLoc.u < searchDiameter
2520 		        &&  objLoc.v >= 0 && objLoc.v < searchDiameter) {
2521 			(*cellArray1)[objLoc.u][objLoc.v] = cellOccupied;
2522 		}
2523 	}
2524 
2525 	//  Push the starting location in the center of the array.
2526 
2527 	spush(TilePoint(searchCenter, searchCenter, startingCoords.z),
2528 	      1,                              // initial cost is 1
2529 	      0);                             // facing irrelevant
2530 
2531 	while (squeue->remove(qi)) {
2532 		TilePoint   centerTileCoords,
2533 		            distVector;
2534 		StaticTilePoint *tDir;
2535 		int16       dir;
2536 		int32       distFromCenter,
2537 		            rating;
2538 
2539 		//  Calculate the distance between the current point and
2540 		//  the center of the search
2541 		distFromCenter =
2542 		    quickDistance(
2543 		        Point32(qi.u - searchCenter, qi.v - searchCenter));
2544 //				max( ABS( qi.u - searchCenter ), ABS( qi.v - searchCenter ) );
2545 
2546 		//  Calculate the "goodness" of this cell -- is it in the
2547 		//  middle of the band between min and max?
2548 		rating = MIN(distFromCenter - minDist, maxDist - distFromCenter);
2549 
2550 		//  Calculate the coordinates of the center in tiles
2551 		centerTileCoords.u = qi.u + baseTileCoords.u;
2552 		centerTileCoords.v = qi.v + baseTileCoords.v;
2553 		centerTileCoords.z = 0;
2554 
2555 		centerPt.u = (centerTileCoords.u << kTileUVShift) + kTileUVSize / 2;
2556 		centerPt.v = (centerTileCoords.v << kTileUVShift) + kTileUVSize / 2;
2557 		centerPt.z = qi.z;
2558 
2559 		//  If this is the best cell found so far, and it is not
2560 		//  occupied, then mark it as the best cell.
2561 		if (rating > bestRating
2562 		        &&  !((*cellArray1)[qi.u][qi.v] & cellOccupied)) {
2563 			bool    cellOK = true;
2564 
2565 			//  if this point is on-screen, we might want to reject it...
2566 			if (offScreenOnly) {
2567 				Point16 screenCoords;           // screen coordinates
2568 
2569 				//  Convert to XY coords.
2570 				TileToScreenCoords(centerPt, screenCoords);
2571 
2572 				//  If the point is on-screen, then reject it.
2573 				//  (We want the monsters to walk in from off-screen,
2574 				//  not 'pop in').
2575 				if (screenCoords.x >= -16 && screenCoords.x <= kTileRectWidth + 16
2576 				        &&  screenCoords.y >= -16 && screenCoords.y <= kTileRectHeight + 80) {
2577 					cellOK = false;
2578 				}
2579 			}
2580 
2581 			//  Save closest point encountered.
2582 			if (cellOK) {
2583 				bestLoc.u = qi.u;
2584 				bestLoc.v = qi.v;
2585 				bestLoc.z = qi.z;
2586 				bestRating = rating;
2587 
2588 				if (rating >= bestPossible) break;
2589 			}
2590 		}
2591 
2592 		if (distFromCenter >= maxDist) continue;
2593 
2594 		for (dir = dirUpLeft;
2595 		        dir <= dirUpRight;
2596 		        dir += 2) {
2597 			uint32          terrain;
2598 			uint8           *cell;
2599 			TilePoint       testPt;
2600 			StandingTileInfo sti;
2601 			TilePoint       fromSubPt,
2602 			                toSubPt;
2603 			bool            traversable = true;
2604 			int16           i;
2605 
2606 			uint16          *moveMask = &sTerrainMasks[dir - 1];
2607 
2608 			tDir = &tDirTable2[dir];
2609 			cell = &(*cellArray1)[qi.u + tDir->u][qi.v + tDir->v];
2610 
2611 			//  Only visit each cell once. Do this before terrain
2612 			//  is checked, to save time.
2613 			if (*cell & cellVisited) continue;
2614 
2615 			testPt = centerPt + tDirTable3[dir];
2616 
2617 			//  Get info about the terrain at that point
2618 			terrain =   tileTerrain(mapNum,
2619 			                        centerTileCoords,
2620 			                        moveMask[0],
2621 			                        centerPt.z + 8,
2622 			                        centerPt.z + 68);
2623 
2624 			//  Reject if we can't move
2625 			if (terrain & (terrainImpassable | terrainRaised)) {
2626 				//  But only if this is isn't the center point
2627 				if (distFromCenter > 0) continue;
2628 			}
2629 
2630 			//  Get the height of the terrain at the new point
2631 			testPt.z =  tileSlopeHeight(testPt, mapNum, 68, &sti);
2632 
2633 			//  If it's too high to step, then don't continue
2634 //			if (testPt.z - qi.z > kMaxStepHeight) continue;
2635 			fromSubPt = centerPt;
2636 			for (i = 0; i < kTileSubSize; i++) {
2637 				int16       deltaZ;
2638 
2639 				//  Next sub tile
2640 				toSubPt = fromSubPt + tDirTable[dir];
2641 				toSubPt.z = tileSlopeHeight(toSubPt, mapNum, 68);
2642 
2643 				deltaZ = toSubPt.z - fromSubPt.z;
2644 
2645 				//  If it's too high to step, then don't continue
2646 				if (deltaZ > kMaxStepHeight || deltaZ < -(kMaxStepHeight * 2)) {
2647 					traversable = false;
2648 					break;
2649 				}
2650 
2651 				fromSubPt = toSubPt;
2652 			}
2653 
2654 			if (!traversable) continue;
2655 
2656 
2657 			//  Get info about terrain at new point
2658 			terrain =   tileTerrain(mapNum,
2659 			                        centerTileCoords + *tDir,
2660 			                        moveMask[1],
2661 			                        testPt.z + 8,
2662 			                        testPt.z + 68);
2663 
2664 			//  Reject if terrain allows no entry
2665 			if (terrain & (terrainImpassable | terrainRaised)) continue;
2666 
2667 #if VISUAL6
2668 			TPLine(centerPt, testPt);
2669 #endif
2670 
2671 			*cell |= cellVisited;
2672 
2673 			//  Push the new point onto the queue->
2674 			//  (Cost is random so as to allow site selection to
2675 			//  be somewhat non-deterministic).
2676 			spush(TilePoint(qi.u + tDir->u,
2677 			                qi.v + tDir->v,
2678 			                testPt.z),
2679 			      qi.cost + g_vm->_rnd->getRandomNumber(3),
2680 			      dir);
2681 		}
2682 	}
2683 
2684 	free(cellArray1);
2685 
2686 	return  bestLoc != Nowhere
2687 	        ?   TilePoint(
2688 	            ((bestLoc.u + baseTileCoords.u) << kTileUVShift) + kTileUVSize / 2,
2689 	            ((bestLoc.v + baseTileCoords.v) << kTileUVShift) + kTileUVSize / 2,
2690 	            bestLoc.z)
2691 	        :   Nowhere;
2692 }
2693 
checkPath(ObjectID worldID,uint8 height,const TilePoint & startingPt,const TilePoint & destPt)2694 bool checkPath(
2695     ObjectID            worldID,
2696     uint8               height,
2697     const TilePoint     &startingPt,
2698     const TilePoint     &destPt) {
2699 	TilePoint       startingCoords = startingPt,
2700 	                destCoords = destPt,
2701 	                startingTileCoords,
2702 	                destTileCoords;
2703 	TilePoint       baseCoords,
2704 	                baseTileCoords,
2705 	                centerPt;               // The current center coordinates
2706 	int           minTileRegU,
2707 	              minTileRegV,
2708 	              maxTileRegU,
2709 	              maxTileRegV,
2710 	              curTileRegU,
2711 	              curTileRegV;
2712 
2713 	int16           mapNum = GameWorld::IDtoMapNum(worldID);
2714 
2715 	QueueItem       qi;
2716 
2717 	StandingTileInfo sti;
2718 
2719 	startingTileCoords.u = startingCoords.u >> kTileUVShift;
2720 	startingTileCoords.v = startingCoords.v >> kTileUVShift;
2721 	startingTileCoords.z = 0;
2722 
2723 	destTileCoords.u = destCoords.u >> kTileUVShift;
2724 	destTileCoords.v = destCoords.v >> kTileUVShift;
2725 	destTileCoords.z = 0;
2726 
2727 	//  Quantize destination coords to nearest tile center
2728 	destCoords.u = (destTileCoords.u << kTileUVShift) + kTileUVSize / 2;
2729 	destCoords.v = (destTileCoords.v << kTileUVShift) + kTileUVSize / 2;
2730 	destCoords.z = tileSlopeHeight(destCoords, mapNum, height);
2731 
2732 	//  Determine if destination is outside the search region
2733 	if (destTileCoords.u < startingTileCoords.u - searchCenter
2734 	        ||  destTileCoords.u >= startingTileCoords.u + searchCenter
2735 	        ||  destTileCoords.v < startingTileCoords.v - searchCenter
2736 	        ||  destTileCoords.v >= startingTileCoords.v + searchCenter)
2737 		return false;
2738 
2739 	//  Allocate the array of cells
2740 	SimpleCellArray *cellArray1 = (SimpleCellArray *)malloc(sizeof(*cellArray1));
2741 	if (cellArray1 == nullptr)
2742 		return false;
2743 
2744 	//  Calculate where search cells will be projected onto map
2745 	baseTileCoords.u = startingTileCoords.u - searchCenter;
2746 	baseTileCoords.v = startingTileCoords.v - searchCenter;
2747 	baseTileCoords.z = 0;
2748 
2749 	baseCoords.u = baseTileCoords.u << kTileUVShift;
2750 	baseCoords.v = baseTileCoords.v << kTileUVShift;
2751 	baseCoords.z = 0;
2752 
2753 	//  Clear the search array and the queue
2754 	memset(cellArray1, cellUnvisited, sizeof(* cellArray1));
2755 	squeue->clear();
2756 
2757 	//  Push the starting location in the center of the array.
2758 	minTileRegU = (startingCoords.u - kTileUVSize / 2) >> kTileUVShift;
2759 	minTileRegV = (startingCoords.v - kTileUVSize / 2) >> kTileUVShift;
2760 	maxTileRegU = (startingCoords.u + kTileUVSize / 2 + kTileUVMask)
2761 	              >>  kTileUVShift;
2762 	maxTileRegV = (startingCoords.v + kTileUVSize / 2 + kTileUVMask)
2763 	              >>  kTileUVShift;
2764 
2765 	for (curTileRegU = minTileRegU;
2766 	        curTileRegU < maxTileRegU;
2767 	        curTileRegU++) {
2768 		for (curTileRegV = minTileRegV;
2769 		        curTileRegV < maxTileRegV;
2770 		        curTileRegV++) {
2771 			TilePoint       quantizedCoords,
2772 			                offsetVector;
2773 			int16           dist,
2774 			                zDist,
2775 			                cost;
2776 
2777 			//  Quantize this tile position to the tile center
2778 			quantizedCoords.u = (curTileRegU << kTileUVShift) + kTileUVSize / 2;
2779 			quantizedCoords.v = (curTileRegV << kTileUVShift) + kTileUVSize / 2;
2780 			quantizedCoords.z = startingCoords.z;
2781 			quantizedCoords.z = tileSlopeHeight(quantizedCoords, mapNum, height);
2782 
2783 			//  If the height difference is too great skip this tile
2784 			//  position
2785 			if (ABS(quantizedCoords.z - startingCoords.z) > kMaxStepHeight)
2786 				continue;
2787 
2788 			//  Compute initial cost based upon the distance from the
2789 			//  starting location
2790 			offsetVector = quantizedCoords - startingCoords;
2791 			dist = offsetVector.quickHDistance();
2792 			zDist = ABS(offsetVector.z);
2793 			cost = dist + zDist;
2794 
2795 			//  Push this point
2796 			spush(
2797 			    TilePoint(
2798 			        curTileRegU - baseTileCoords.u,
2799 			        curTileRegV - baseTileCoords.v,
2800 			        quantizedCoords.z),
2801 			    cost + 1,
2802 			    0);
2803 		}
2804 	}
2805 
2806 	while (squeue->remove(qi)) {
2807 		TilePoint   centerTileCoords;
2808 		StaticTilePoint *tDir;
2809 		int16       centerDistFromDest;
2810 		int       dir;
2811 
2812 		//  Calculate the coordinates of the center in tiles
2813 		centerTileCoords.u = qi.u + baseTileCoords.u;
2814 		centerTileCoords.v = qi.v + baseTileCoords.v;
2815 		centerTileCoords.z = 0;
2816 
2817 		centerPt.u = (centerTileCoords.u << kTileUVShift) + kTileUVSize / 2;
2818 		centerPt.v = (centerTileCoords.v << kTileUVShift) + kTileUVSize / 2;
2819 		centerPt.z = qi.z;
2820 
2821 		centerDistFromDest = (centerPt - destCoords).quickHDistance();
2822 
2823 		for (dir = dirUpLeft;
2824 		        dir <= dirUpRight;
2825 		        dir += 2) {
2826 			uint32          terrain;
2827 			uint8           *cell;
2828 			TilePoint       testPt,
2829 			                testTileCoords,
2830 			                fromSubPt,
2831 			                toSubPt;
2832 			int16           testDistFromDest,
2833 			                deltaDistFromDest;
2834 			int           i;
2835 			bool            traversable = true;
2836 
2837 			uint16          *moveMask = &sTerrainMasks[dir - 1];
2838 
2839 			tDir = &tDirTable2[dir];
2840 
2841 			testTileCoords.u = centerTileCoords.u + tDir->u;
2842 			testTileCoords.v = centerTileCoords.v + tDir->v;
2843 			testTileCoords.z = 0;
2844 
2845 			cell = &(*cellArray1)[qi.u + tDir->u][qi.v + tDir->v];
2846 
2847 			//  Only visit each cell once..
2848 			if (*cell & cellVisited) continue;
2849 
2850 			testPt = centerPt + tDirTable3[dir];
2851 
2852 			testDistFromDest = (testPt - destCoords).quickHDistance();
2853 			deltaDistFromDest = testDistFromDest - centerDistFromDest;
2854 
2855 			//  Get info about the terrain at that point
2856 			terrain =   tileTerrain(mapNum,
2857 			                        centerTileCoords,
2858 			                        moveMask[0],
2859 			                        centerPt.z + 8,
2860 			                        centerPt.z + height);
2861 
2862 			//  Reject if we can't move
2863 			if (terrain & (terrainImpassable | terrainRaised)) continue;
2864 
2865 			//  Get the height of the terrain at the new point
2866 			testPt.z =  tileSlopeHeight(testPt, mapNum, height, &sti);
2867 
2868 			fromSubPt = centerPt;
2869 			for (i = 0; i < kTileSubSize; i++) {
2870 				int16       deltaZ;
2871 
2872 				//  Next sub tile
2873 				toSubPt = fromSubPt + tDirTable[dir];
2874 				toSubPt.z = tileSlopeHeight(toSubPt, mapNum, height);
2875 
2876 				deltaZ = toSubPt.z - fromSubPt.z;
2877 
2878 				//  If it's too high to step, then don't continue
2879 				if (deltaZ > kMaxStepHeight || deltaZ < -(kMaxStepHeight * 2)) {
2880 					traversable = false;
2881 					break;
2882 				}
2883 
2884 				fromSubPt = toSubPt;
2885 			}
2886 
2887 			if (!traversable) continue;
2888 
2889 			//  Get info about terrain at new point
2890 			terrain =   tileTerrain(mapNum,
2891 			                        centerTileCoords + *tDir,
2892 			                        moveMask[1],
2893 			                        testPt.z + 8,
2894 			                        testPt.z + height);
2895 
2896 			//  Reject if terrain allows no entry
2897 			if (terrain & (terrainImpassable | terrainRaised)) continue;
2898 
2899 #if VISUAL7
2900 			TPLine(centerPt, testPt);
2901 #endif
2902 
2903 			*cell |= cellVisited;
2904 
2905 			//  If we're there, we're done
2906 			if (testTileCoords == destTileCoords) {
2907 				free(cellArray1);
2908 
2909 				//  If the resulting height is significantly different
2910 				//  from the destination height, assume we're on a
2911 				//  different level and return false.
2912 				return ABS(testPt.z - destCoords.z) <= kMaxStepHeight;
2913 			}
2914 
2915 
2916 			//  Push the new point onto the queue->
2917 			spush(TilePoint(qi.u + tDir->u,
2918 			                qi.v + tDir->v,
2919 			                testPt.z),
2920 			      qi.cost + (deltaDistFromDest + kTileUVSize) / 4,
2921 			      dir);
2922 		}
2923 	}
2924 
2925 	free(cellArray1);
2926 
2927 	//  If we're here we've haven't found a path
2928 	return false;
2929 }
2930 
2931 
2932 /* ===================================================================== *
2933    Path finder management functions
2934  * ===================================================================== */
2935 
initPathFinder(void)2936 void initPathFinder(void) {
2937 	queue = new PriorityQueue<QueueItem, 192>;
2938 	squeue = new PriorityQueue<QueueItem, 128>;
2939 	objectVolumeArray = new TileRegion[128];
2940 
2941 	pathTileArray = (PathTilePosArray *)malloc( sizeof *pathTileArray);
2942 	maskComp = new MaskComputer;
2943 	cellArray = new PathArray;
2944 	PathRequest::tileArray = new PathTileRegion;
2945 }
2946 
cleanupPathFinder(void)2947 void cleanupPathFinder(void) {
2948 	if (pathTileArray) {
2949 		free(pathTileArray);
2950 		pathTileArray = nullptr;
2951 	}
2952 	if (maskComp) {
2953 		delete maskComp;
2954 		maskComp = nullptr;
2955 	}
2956 	if (cellArray != nullptr) {
2957 		delete cellArray;
2958 		cellArray = nullptr;
2959 	}
2960 
2961 	delete queue;
2962 	delete squeue;
2963 	delete[] objectVolumeArray;
2964 	delete PathRequest::tileArray;
2965 }
2966 
2967 } // end of namespace Saga2
2968