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