scummvm/engines/saga2/path.cpp
2021-12-26 18:48:43 +01:00

2967 lines
84 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/dispnode.h"
#include "saga2/tile.h"
#include "saga2/motion.h"
#include "saga2/player.h"
#include "saga2/cmisc.h"
#include "saga2/priqueue.h"
namespace Saga2 {
// Defines to turn on visual debugging aids for path finder.
#define VISUAL1 0
//1 for paths considered
#define VISUAL2 0
//2 unused
#define VISUAL3 0
//3 unused
#define VISUAL4 0
//4 for fetchTileSection
#define VISUAL5 0
//5 for fetchSubMeta
#define VISUAL6 0
//6 for selectNearbySite
#define VISUAL7 0
//7 for checkPath
#if VISUAL1 || VISUAL2 || VISUAL4 || VISUAL5 || VISUAL6 || VISUAL7
void TPLine(const TilePoint &start, const TilePoint &stop);
#endif
/* ===================================================================== *
Imports
* ===================================================================== */
extern WorldMapData *mapList;
/* ===================================================================== *
Types
* ===================================================================== */
const int searchCenter = 13, // origin point of array
searchDiameter = searchCenter * 2;
const int straightBaseCost = 4,
diagBaseCost = 6,
straightEasyCost = straightBaseCost + 2,
straightNormalCost = straightBaseCost + 4,
straightHardCost = straightBaseCost + 8,
diagEasyCost = diagBaseCost + 3,
diagNormalCost = diagBaseCost + 6,
diagHardCost = diagBaseCost + 12;
const int subMetaSize = 4,
subMetaShift = 2,
subMetaMask = subMetaSize - 1;
static StaticTilePoint tDirTable[8] = {
{ 4, 4, 0},
{ 0, 4, 0},
{-4, 4, 0},
{-4, 0, 0},
{-4, -4, 0},
{ 0, -4, 0},
{ 4, -4, 0},
{ 4, 0, 0}
};
static StaticTilePoint tDirTable2[8] = {
{ 1, 1, 0},
{ 0, 1, 0},
{-1, 1, 0},
{-1, 0, 0},
{-1, -1, 0},
{ 0, -1, 0},
{ 1, -1, 0},
{ 1, 0, 0}
};
static StaticTilePoint tDirTable3[8] = {
{ 16, 16, 0},
{ 0, 16, 0},
{-16, 16, 0},
{-16, 0, 0},
{-16, -16, 0},
{ 0, -16, 0},
{ 16, -16, 0},
{ 16, 0, 0}
};
/* ===================================================================== *
The PathTileRegion class
* ===================================================================== */
struct PathTileInfo {
TileInfo *surfaceTile;
int16 surfaceHeight;
PathTileInfo() : surfaceTile(nullptr), surfaceHeight(0) {}
};
typedef PathTileInfo PathTilePosInfo[maxPlatforms];
typedef PathTilePosInfo PathTilePosArray
[searchDiameter + 4]
[searchDiameter + 4];
typedef uint8 PathSubMetaFlags
[((((searchDiameter
+ subMetaMask + 4)
* (searchDiameter
+ subMetaMask + 4))
>> subMetaShift)
+ 7)
>> 3];
// This class manages an array containing terrain information, which
// will be loaded on the fly as the path finder needs it.
class PathTileRegion {
friend class PathRequest;
int16 mapNum; // The map number of the terrain data
TilePoint origin, // The base tile coords of the array
area, // The size of the array in tiles
subMetaArea; // The number of submetatiles overlaying
// the array area
PathTilePosInfo *array; // The pointer to the array
uint8 *subMetaFlags; // A bit array indicating which
// submetatiles have been loaded
void fetchSubMeta(const TilePoint &subMeta);
public:
PathTileRegion() {
mapNum = 0;
array = nullptr;
subMetaFlags = nullptr;
}
void init(
int16 map,
const TilePoint &org,
const TilePoint &a,
PathTilePosInfo *arr,
uint8 *flagArray);
void fetchTileSection(const TilePoint &org, const TilePoint &a);
PathTilePosInfo *tilePos(const TilePoint &pos) {
assert(pos.u >= origin.u && (pos.u - origin.u) < area.u);
assert(pos.v >= origin.v && (pos.v - origin.v) < area.v);
return &array[(pos.u - origin.u) * area.v + pos.v - origin.v];
}
};
// Initialize the path tile region
void PathTileRegion::init(
int16 map,
const TilePoint &org,
const TilePoint &a,
PathTilePosInfo *arr,
uint8 *flagArray) {
mapNum = map;
origin = org;
area = a;
array = arr;
subMetaFlags = flagArray;
origin.z = area.z = subMetaArea.z = 0;
subMetaArea.u = (area.u + (origin.u & subMetaMask) + subMetaMask)
>> subMetaShift;
subMetaArea.v = (area.v + (origin.v & subMetaMask) + subMetaMask)
>> subMetaShift;
// clear all of the submetatile flags
memset(subMetaFlags, 0, (subMetaArea.u * subMetaArea.v + 7) >> 3);
// nullptr the tile pointers in the array
int16 arraySize = area.u * area.v;
PathTilePosInfo *tiPtr = array;
for (; arraySize > 0; arraySize--, tiPtr++) {
PathTilePosInfo &ptpi = *tiPtr;
for (int i = 0; i < maxPlatforms; i++)
ptpi[i].surfaceTile = nullptr;
}
}
// This function will load the submetatiles which overlay a specified
// region into the array, if those submetatiles have not already been
// loaded.
void PathTileRegion::fetchTileSection(const TilePoint &org,
const TilePoint &a) {
int16 flagIndex;
int u, v;
TilePoint secSubMetaOrigin,
secSubMetaArea,
relSubMeta;
#if VISUAL4
TilePoint pt1, pt2;
pt1 = pt2 = org << kTileUVShift;
pt1.z = pt2.z = 0;
pt2.u += (a.u << kTileUVShift);
TPLine(pt1, pt2);
pt1.u = pt2.u;
pt1.v += (a.v << kTileUVShift);
TPLine(pt1, pt2);
pt2.v = pt1.v;
pt2.u -= (a.u << kTileUVShift);
TPLine(pt1, pt2);
pt1.u = pt2.u;
pt1.v -= (a.v << kTileUVShift);
TPLine(pt1, pt2);
#endif
// Determine the origin and area of the specified tile section
// in submetatile coordinates
secSubMetaOrigin.u = (org.u >> subMetaShift);
secSubMetaOrigin.v = (org.v >> subMetaShift);
secSubMetaArea.u = (a.u + (org.u & subMetaMask) + subMetaMask)
>> subMetaShift;
secSubMetaArea.v = (a.v + (org.v & subMetaMask) + subMetaMask)
>> subMetaShift;
for (u = 0; u < secSubMetaArea.u; u++) {
relSubMeta.u = secSubMetaOrigin.u - (origin.u >> subMetaShift) + u;
relSubMeta.v = secSubMetaOrigin.v - (origin.v >> subMetaShift);
flagIndex = relSubMeta.u * subMetaArea.v + relSubMeta.v;
for (v = 0; v < secSubMetaArea.v; v++, flagIndex++) {
// Check the submetatile flag in the bit array
if (!(subMetaFlags[flagIndex >> 3] & (1 << (flagIndex & 7)))) {
// Load the submetatile and set its flag
fetchSubMeta(TilePoint(secSubMetaOrigin.u + u,
secSubMetaOrigin.v + v,
0));
subMetaFlags[flagIndex >> 3] |= 1 << (flagIndex & 7);
}
}
}
}
// This function will load a submeta tile in the array
void PathTileRegion::fetchSubMeta(const TilePoint &subMeta) {
WorldMapData *map = &mapList[mapNum];
TilePoint mCoords;
MetaTile *mt;
mCoords.u = subMeta.u >> 1;
mCoords.v = subMeta.v >> 1;
mCoords.z = 0;
mt = map->lookupMeta(mCoords);
#if VISUAL5
TilePoint pt1, pt2;
pt1 = pt2 = subMeta << (subMetaShift + kTileUVShift);
pt1.z = pt2.z = 0;
pt2.u += (subMetaSize << kTileUVShift);
TPLine(pt1, pt2);
pt1.u = pt2.u;
pt1.v += (subMetaSize << kTileUVShift);
TPLine(pt1, pt2);
pt2.v = pt1.v;
pt2.u -= (subMetaSize << kTileUVShift);
TPLine(pt1, pt2);
pt1.u = pt2.u;
pt1.v -= (subMetaSize << kTileUVShift);
TPLine(pt1, pt2);
#endif
if (mt) {
TileRegion tileReg;
TilePoint offset;
tileReg.min.u = (subMeta.u << subMetaShift) - origin.u;
offset.u = tileReg.min.u + subMetaSize < area.u
? subMetaSize
: area.u - tileReg.min.u;
tileReg.min.v = (subMeta.v << subMetaShift) - origin.v;
offset.v = tileReg.min.v + subMetaSize < area.v
? subMetaSize
: area.v - tileReg.min.v;
if (tileReg.min.u < 0) {
offset.u += tileReg.min.u;
tileReg.min.u = 0;
}
if (tileReg.min.v < 0) {
offset.v += tileReg.min.v;
tileReg.min.v = 0;
}
// Compute tile region relative to metatile
tileReg.min.u = (tileReg.min.u + origin.u) & kPlatMask;
tileReg.max.u = tileReg.min.u + offset.u;
tileReg.min.v = (tileReg.min.v + origin.v) & kPlatMask;
tileReg.max.v = tileReg.min.v + offset.v;
assert(tileReg.max.u <= kPlatformWidth);
assert(tileReg.max.v <= kPlatformWidth);
// Compute the offset of base tile in metatile to origin
offset.u = ((subMeta.u >> 1) << kPlatShift) - origin.u;
offset.v = ((subMeta.v >> 1) << kPlatShift) - origin.v;
for (int i = 0; i < maxPlatforms; i++) {
uint16 tpFlags = 0;
Platform *p;
int u, v;
TileRef *tr;
int16 height;
if ((p = mt->fetchPlatform(mapNum, i)) == nullptr)
continue;
if (!(p->flags & plVisible)) continue;
for (u = tileReg.min.u; u < tileReg.max.u; u++) {
PathTilePosInfo *arrRow = &array[(u + offset.u) * area.v];
assert(u >= 0);
assert(u < kPlatformWidth);
for (v = tileReg.min.v; v < tileReg.max.v; v++) {
int16 flagIndex = ((u & subMetaMask) << subMetaShift) | (v & subMetaMask);
assert(v >= 0);
assert(v < kPlatformWidth);
if (!(tpFlags & (1 << flagIndex))) {
tpFlags |= (1 << flagIndex);
tr = &p->tiles[u][v];
height = tr->tileHeight << 3;
if (tr->flags & trTileTAG) {
ActiveItem *groupItem,
*instanceItem;
int16 state = 0;
int16 tagU, tagV;
int tempU, tempV;
TilePoint absPos;
TileRegion subMetaTag;
TileRef *stateData;
assert((uint16)tr->tile <= activeItemIndexNullID);
groupItem = ActiveItem::activeItemAddress(
ActiveItemID(mapNum, tr->tile));
tagU = u - ((tr->flags >> 1) & 0x07);
tagV = v - ((tr->flags >> 4) & 0x07);
subMetaTag.min.u = tagU;
subMetaTag.max.u = tagU + groupItem->_data.group.uSize;
subMetaTag.min.v = tagV;
subMetaTag.max.v = tagV + groupItem->_data.group.vSize;
if (subMetaTag.min.u < tileReg.min.u)
subMetaTag.min.u = tileReg.min.u;
if (subMetaTag.max.u > tileReg.max.u)
subMetaTag.max.u = tileReg.max.u;
if (subMetaTag.min.v < tileReg.min.v)
subMetaTag.min.v = tileReg.min.v;
if (subMetaTag.max.v > tileReg.max.v)
subMetaTag.max.v = tileReg.max.v;
// Abspos is the absolute position of the
// group on the tile map.
absPos.u = (mCoords.u << kPlatShift) | tagU;
absPos.v = (mCoords.v << kPlatShift) | tagV;
absPos.z = height;
// Look up the group instance in the hash.
instanceItem = map->findHashedInstance(absPos, tr->tile);
if (instanceItem) state = instanceItem->getInstanceState(mapNum);
stateData = &(map->activeItemData)[
groupItem->_data.group.grDataOffset
+ state * groupItem->_data.group.animArea];
for (tempU = subMetaTag.min.u; tempU < subMetaTag.max.u; tempU++) {
TileRef *rowData = &stateData[(tempU - tagU) * groupItem->_data.group.vSize];
PathTilePosInfo *tempArrRow = &array[(tempU + offset.u) * area.v];
for (tempV = subMetaTag.min.v; tempV < subMetaTag.max.v; tempV++) {
flagIndex = ((tempU & subMetaMask) << subMetaShift) + (tempV & subMetaMask);
tpFlags |= (1 << flagIndex);
if (instanceItem) tr = &rowData[tempV - tagV];
#if DEBUG
else {
static TileRef dummyRef = { 1, 0, 0 };
tr = &dummyRef;
}
#endif
tempArrRow[tempV + offset.v][i].surfaceTile =
TileInfo::tileAddress(tr->tile);
tempArrRow[tempV + offset.v][i].surfaceHeight =
height + (tr->tileHeight << 3);
}
}
} else {
arrRow[v + offset.v][i].surfaceTile =
TileInfo::tileAddress(tr->tile);
arrRow[v + offset.v][i].surfaceHeight =
height;
}
}
}
}
}
}
}
/* ===================================================================== *
The PathArray and QueueItem types
* ===================================================================== */
// Represents a single cell on the path array
struct PathCell {
uint8 direction; // direction into cell
int8 platformDelta; // platform difference of cell
int16 height; // height above floor
int16 cost; // cost to get there
};
// Virtual array of cells representing the path region we will search.
class PathArray {
public:
enum {
chunkTileDiameter = 4,
regionChunkDiameter = (searchDiameter + chunkTileDiameter - 1) / chunkTileDiameter
};
private:
// Search sub-region class.
struct PathArrayChunk {
uint16 mask; // Mask indicating which cells in chunk are
// allocated
// The cell array
PathCell array[chunkTileDiameter][chunkTileDiameter];
PathArrayChunk() : mask(0) {}
};
// Master array of chunk pointers
PathArrayChunk *array[maxPlatforms][regionChunkDiameter][regionChunkDiameter];
public:
// Exception class
class CellAllocationFailure {};
// Constructor
PathArray();
// Destructor
~PathArray();
// Make a new cell or access an existing cell. If the specified
// cell already exists *newCell will be set to false, else it will
// be true. If it fails to allocate a new cell it will throw
// a CellAllocationFailure.
PathCell *makeCell(int plat, int uCoord, int vCoord, bool *newCell);
// Get a pointer to an existing cell. If the specified cell has
// not been created, it will return nullptr.
PathCell *getCell(int plat, int uCoord, int vCoord);
// Delete an existing cell
void deleteCell(int plat, int uCoord, int vCoord);
// Delete all existing cells
void reset();
};
// Constructor
PathArray::PathArray() {
int plat, chunkU, chunkV;
for (plat = 0; plat < maxPlatforms; plat++) {
for (chunkU = 0; chunkU < regionChunkDiameter; chunkU++) {
for (chunkV = 0; chunkV < regionChunkDiameter; chunkV++)
array[plat][chunkU][chunkV] = nullptr;
}
}
}
// Destructor
PathArray::~PathArray() {
reset();
}
// Make a new cell or access an existing cell. If the specified
// cell already exists *newCell will be set to false, else it will
// be true. If it fails to allocate a new cell it will throw
// a CellAllocationFailure.
PathCell *PathArray::makeCell(int plat, int uCoord, int vCoord, bool *newCell) {
assert(plat >= 0 && plat < maxPlatforms);
assert(uCoord >= 0 && uCoord < searchDiameter);
assert(vCoord >= 0 && vCoord < searchDiameter);
assert(newCell != nullptr);
// Compute the chunk coords
int chunkUCoord = uCoord >> 2,
chunkVCoord = vCoord >> 2;
// Get a pointer to the chunk pointer in the array
PathArrayChunk **chunkPtrPtr = &array[plat][chunkUCoord][chunkVCoord];
// Get existing chunk or allocate a new one
if (*chunkPtrPtr != nullptr || (*chunkPtrPtr = new PathArrayChunk) != nullptr) {
PathArrayChunk *chunkPtr = *chunkPtrPtr;
uint16 chunkCellMask;
// Compute the coordinates of the cell relative to the chunk
uCoord &= chunkTileDiameter - 1;
vCoord &= chunkTileDiameter - 1;
// Compute the bit mask of the cell within the chunk
chunkCellMask = 1 << ((uCoord << 2) | vCoord);
// Determine if this is a new cell
*newCell = (chunkPtr->mask & chunkCellMask) == 0;
// Mark the cell as allocated
chunkPtr->mask |= chunkCellMask;
return &chunkPtr->array[uCoord][vCoord];
} else {
// Failed to allocate cell so throw exception
error("Cell Allocation failure");
}
}
// Get a pointer to an existing cell. If the specified cell has
// not been created, it will return nullptr.
PathCell *PathArray::getCell(int plat, int uCoord, int vCoord) {
assert(plat >= 0 && plat < maxPlatforms);
assert(uCoord >= 0 && uCoord < searchDiameter);
assert(vCoord >= 0 && vCoord < searchDiameter);
// Compute the chunk coords
int chunkUCoord = uCoord >> 2,
chunkVCoord = vCoord >> 2;
uint16 chunkCellMask;
PathArrayChunk *chunkPtr = array[plat][chunkUCoord][chunkVCoord];
if (chunkPtr == nullptr) return nullptr;
// Compute the coordinates of the cell relative to the chunk
uCoord &= chunkTileDiameter - 1;
vCoord &= chunkTileDiameter - 1;
// Compute the bit mask of the cell within the chunk
chunkCellMask = 1 << ((uCoord << 2) | vCoord);
// Determine if cell has been allocated
if ((chunkPtr->mask & chunkCellMask) == 0)
return nullptr;
return &chunkPtr->array[uCoord][vCoord];
}
void PathArray::deleteCell(int plat, int uCoord, int vCoord) {
assert(plat >= 0 && plat < maxPlatforms);
assert(uCoord >= 0 && uCoord < searchDiameter);
assert(vCoord >= 0 && vCoord < searchDiameter);
// Compute the chunk coords
int chunkUCoord = uCoord >> 2,
chunkVCoord = vCoord >> 2;
uint16 chunkCellMask;
PathArrayChunk *chunkPtr = array[plat][chunkUCoord][chunkVCoord];
if (chunkPtr == nullptr)
return;
// Compute the coordinates of the cell relative to the chunk
uCoord &= chunkTileDiameter - 1;
vCoord &= chunkTileDiameter - 1;
// Compute the bit mask of the cell within the chunk
chunkCellMask = 1 << ((uCoord << 2) | vCoord);
// Clear the bit
chunkPtr->mask &= ~chunkCellMask;
}
// Delete all existing cells
void PathArray::reset() {
int plat, chunkU, chunkV;
for (plat = 0; plat < maxPlatforms; plat++) {
for (chunkU = 0; chunkU < regionChunkDiameter; chunkU++) {
for (chunkV = 0; chunkV < regionChunkDiameter; chunkV++) {
PathArrayChunk **chunkPtrPtr;
chunkPtrPtr = &array[plat][chunkU][chunkV];
if (*chunkPtrPtr != nullptr) {
delete *chunkPtrPtr;
*chunkPtrPtr = nullptr;
}
}
}
}
}
// Represents an entry in the queue
struct QueueItem {
int16 z; // height over terrain
uint8 u, v; // relative coords of cell
uint8 platform; // platform number of cell
Direction direction; // direction out of cell
uint8 pad;
int16 cost; // Cost to get to this cell
QueueItem() {
z = 0;
u = v = 0;
platform = 0;
pad = 0;
cost = 0;
direction = 0;
}
operator int() {
return cost;
}
};
/* ===================================================================== *
The MaskComputer class
* ===================================================================== */
// Represents the subtile mask of an object at a single point
struct PointMask {
TilePoint size;
TilePoint offset;
uint16 mask[16];
};
// Represents the subtile masks of each point on a path in a single
// direction
class DirMask {
friend class DirMaskGroup;
PointMask pathPt[4];
public:
PointMask &operator[](int16 index) {
return pathPt[index];
}
};
// Represents the subtile masks of each path in all eight direction
class DirMaskGroup {
friend class MaskComputer;
uint8 crossSection;
DirMask dMask[8];
void computeMask(uint8 objSection);
public:
DirMaskGroup() : crossSection(0) {}
DirMask &operator[](int16 index) {
return dMask[index];
}
};
// The class which will compute all of the subtile masks for an object
class MaskComputer {
private:
DirMaskGroup array[8],
*ptrArray[8];
int16 arraySize;
public:
MaskComputer() : arraySize(0) {
for (int i = 0; i < 8; i++)
ptrArray[i] = nullptr;
}
DirMaskGroup *computeMask(uint8 objSection);
};
// return a word with the bit (minBit) to bit (maxBit-1) all set,
// and all other bits reset.
inline uint32 makeMask16(int minBit, int maxBit) {
return (1 << maxBit) - (1 << minBit);
}
void DirMaskGroup::computeMask(uint8 objSection) {
bool diagExpand;
int dir;
struct {
int16 min;
int16 max;
} area;
crossSection = objSection;
// Calculate the area in subtiles the object occupies. Since U and
// V coordinates will alway equal each other, there is no need to
// calculate both.
area.min = ((kTileUVSize / 2) - objSection) >> kSubTileShift;
area.max = ((kTileUVSize / 2) + objSection + kSubTileMask) >> kSubTileShift;
// Determine if the cross section is wide enough that the diaginal
// masks need to be expanded outward one subtile
diagExpand = (objSection - 1) & 0x2;
for (dir = 0; dir < 8; dir++) {
int ptNum;
TileRegion baseMaskArea;
// Calculate the base mask from which all of the point masks
// will be derived
baseMaskArea.min.u = baseMaskArea.min.v = area.min;
baseMaskArea.max.u = baseMaskArea.max.v = area.max;
if (!(dir & 0x1) && diagExpand) {
switch (dir >> 1) {
case 0:
baseMaskArea.min.u--;
baseMaskArea.max.v--;
break;
case 1:
baseMaskArea.max.u++;
baseMaskArea.min.v--;
break;
case 2:
baseMaskArea.max.u++;
baseMaskArea.max.v++;
break;
case 3:
baseMaskArea.min.u--;
baseMaskArea.max.v++;
}
}
for (ptNum = 0; ptNum < 4; ptNum++) {
int u,
v;
TileRegion ptMaskArea;
uint16 tempMask[16];
PointMask *ptMask = &dMask[dir].pathPt[ptNum];
// Compute the point mask area
ptMaskArea.min = baseMaskArea.min + tDirTable2[dir] * (ptNum + 1);
ptMaskArea.max = baseMaskArea.max + tDirTable2[dir] * (ptNum + 1);
ptMask->offset.u = ptMaskArea.min.u >> kTileSubShift;
ptMask->offset.v = ptMaskArea.min.v >> kTileSubShift;
ptMaskArea.max.u -= ptMaskArea.min.u & ~kSubTileMask;
ptMaskArea.min.u &= kSubTileMask;
ptMaskArea.max.v -= ptMaskArea.min.v & ~kSubTileMask;
ptMaskArea.min.v &= kSubTileMask;
ptMask->size.u = (ptMaskArea.max.u + kTileSubMask) >> kTileSubShift;
ptMask->size.v = (ptMaskArea.max.v + kTileSubMask) >> kTileSubShift;
memset(tempMask, 0, sizeof(tempMask));
uint16 vMask = makeMask16(ptMaskArea.min.v, ptMaskArea.max.v);
for (u = ptMaskArea.min.u; u < ptMaskArea.max.u; u++)
tempMask[u] = vMask;
for (u = 0; u < ptMask->size.u; u++) {
uint16 *srcMask = &tempMask[u << 2];
uint16 *destMask = &ptMask->mask[u << 2];
for (v = 0; v < ptMask->size.v; v++) {
switch (v) {
case 0:
destMask[0] = (srcMask[0] & 0x000f) |
(srcMask[1] & 0x000f) << 4 |
(srcMask[2] & 0x000f) << 8 |
(srcMask[3] & 0x000f) << 12;
break;
case 1:
destMask[1] = (srcMask[0] & 0x00f0) >> 4 |
(srcMask[1] & 0x00f0) |
(srcMask[2] & 0x00f0) << 4 |
(srcMask[3] & 0x00f0) << 8;
break;
case 2:
destMask[2] = (srcMask[0] & 0x0f00) >> 8 |
(srcMask[1] & 0x0f00) >> 4 |
(srcMask[2] & 0x0f00) |
(srcMask[3] & 0x0f00) << 4;
break;
case 3:
destMask[3] = (srcMask[0] & 0xf000) >> 12 |
(srcMask[1] & 0xf000) >> 8 |
(srcMask[2] & 0xf000) >> 4 |
(srcMask[3] & 0xf000);
}
}
}
}
}
}
DirMaskGroup *MaskComputer::computeMask(uint8 crossSection) {
DirMaskGroup *maskGroup;
int i;
// Check if this mask group has already been computed
for (i = 0; i < arraySize; i++) {
maskGroup = ptrArray[i];
if (maskGroup->crossSection == crossSection) {
// This mask group has already been computed
if (i > 0) {
// Move the reference to this mask group up one position
ptrArray[i] = ptrArray[i - 1];
ptrArray[i - 1] = maskGroup;
}
return maskGroup;
}
}
if (arraySize < ARRAYSIZE(array)) {
// Allocate a new place for this mask group
maskGroup = ptrArray[arraySize] = &array[arraySize];
arraySize++;
} else
// Discard last referenced mask group in array
maskGroup = ptrArray[ARRAYSIZE(array) - 1];
// Compute the new group of masks
maskGroup->computeMask(crossSection);
return maskGroup;
}
uint32 tileTerrain(
PathTilePosInfo *tilePos,
int16 mask,
int16 minZ,
int16 maxZ) {
uint32 terrain = 0;
for (int i = 0; i < maxPlatforms; i++) {
int32 height, tileMinZ, tileMaxZ;
TileInfo *ti;
ti = (*tilePos)[i].surfaceTile;
if (ti) {
height = (*tilePos)[i]. surfaceHeight;
TileAttrs &attrs = ti->attrs;
tileMinZ = tileMaxZ = height;
int32 combinedMask = ti->combinedTerrainMask();
if (combinedMask & terrainRaised)
tileMaxZ += attrs.terrainHeight;
if (combinedMask & terrainWater)
tileMinZ -= attrs.terrainHeight;
if (tileMinZ < maxZ
&& tileMaxZ >= minZ) {
uint32 terrainResult = 0,
tileFgdTerrain = (1 << attrs.fgdTerrain),
tileBgdTerrain = (1 << attrs.bgdTerrain);
// If only checking the top of raised terrain treat it
// as if it were normal terrain.
if (minZ + kMaxStepHeight >= tileMaxZ) {
if (tileFgdTerrain & terrainSupportingRaised)
tileFgdTerrain = terrainNormal;
if (tileBgdTerrain & terrainSupportingRaised)
tileBgdTerrain = terrainNormal;
}
if (mask & attrs.terrainMask)
terrainResult |= tileFgdTerrain;
if (mask & ~attrs.terrainMask)
terrainResult |= tileBgdTerrain;
// This prevents actors from walking through
// catwalks and other surfaces which have no bottom.
if ((terrainResult & terrainSolidSurface)
&& height > minZ + kMaxStepHeight) {
terrainResult |= terrainStone;
}
terrain |= terrainResult;
}
}
}
return terrain;
}
const uint32 terrainSink = terrainInsubstantial | terrainSupportingRaised | terrainWater;
int16 tileSlopeHeight(
PathTileRegion &tileArr,
const TilePoint &pt,
GameObject *obj,
PathTileInfo *ptiResult,
uint8 *platformResult) {
// Calculate coordinates of tile and subtile
TilePoint tileCoords = pt >> kTileUVShift,
subTile(
(pt.u >> kSubTileShift) & kSubTileMask,
(pt.v >> kSubTileShift) & kSubTileMask,
0);
PathTileInfo highestTile,
lowestTile;
int supportHeight,
highestSupportHeight,
lowestSupportHeight;
uint32
highestSupportPlatform = 0,
lowestSupportPlatform = 0;
bool highestTileFlag,
lowestTileFlag;
PathTilePosInfo &tilePosInfo = *tileArr.tilePos(tileCoords);
highestSupportHeight = -100;
lowestSupportHeight = 0x7FFF;
highestTileFlag = false;
lowestTileFlag = false;
int objProtHt = obj->proto()->height;
// Search each platform until we find a tile which is under
// the character.
for (int i = 0; i < maxPlatforms; i++) {
PathTileInfo *pti = ((PathTileInfo *)(&tilePosInfo)) + i; // &tilePosInfo[i];
TileInfo *ti = pti->surfaceTile;
if (ti) {
int height = pti->surfaceHeight;
TileAttrs &attrs = ti->attrs;
int16 tileBase = height;
int32 subTileTerrain =
attrs.testTerrain(calcSubTileMask(subTile.u,
subTile.v));
if (subTileTerrain & terrainSink) {
if (subTileTerrain & terrainInsubstantial)
continue;
else if (subTileTerrain & terrainSupportingRaised)
// calculate height of raised surface
supportHeight = height +
attrs.terrainHeight;
else {
// calculate depth of water
supportHeight = height -
attrs.terrainHeight;
tileBase = supportHeight;
}
} else
// calculate height of unraised surface
supportHeight = height +
ptHeight(TilePoint(pt.u & kTileUVMask,
pt.v & kTileUVMask,
0),
attrs.cornerHeight);
// See if the tile is a potential supporting surface
if (tileBase < pt.z + objProtHt
&& supportHeight >= highestSupportHeight
&& (ti->combinedTerrainMask() & (terrainSurface | terrainRaised))) {
highestTileFlag = true;
highestTile = *pti;
highestSupportHeight = supportHeight;
highestSupportPlatform = i;
} else if (!highestTileFlag &&
supportHeight <= lowestSupportHeight &&
(ti->combinedTerrainMask() & (terrainSurface | terrainRaised))) {
lowestTileFlag = true;
lowestTile = *pti;
lowestSupportHeight = supportHeight;
lowestSupportPlatform = i;
}
}
}
if (highestTileFlag) {
if (ptiResult) *ptiResult = highestTile;
if (platformResult) *platformResult = highestSupportPlatform;
return highestSupportHeight;
}
if (lowestTileFlag) {
if (ptiResult) *ptiResult = lowestTile;
if (platformResult) *platformResult = lowestSupportPlatform;
return lowestSupportHeight;
}
if (ptiResult) {
ptiResult->surfaceTile = nullptr;
ptiResult->surfaceHeight = 0;
}
if (platformResult) *platformResult = 0;
return 0;
}
/* ===================================================================== *
PathRequest Class
* ===================================================================== */
enum PathResult {
pathNotDone,
pathDone,
pathAborted
};
// This if the base class for all PathRequests
class PathRequest {
friend void addPathRequestToQueue(PathRequest *pr);
protected:
Actor *actor; // actor path applies to.
int16 smartness; // how intelligent?
MotionTask *mTask; // which motion task started this
uint8 flags;
enum pathFlags {
aborted = (1 << 0), // path request has been aborted
completed = (1 << 1), // pathfinder has found best path
run = (1 << 2)
};
enum {
kPathSize = 16
};
// These static members are initialized when the path request
// becomes the current active request being serviced.
static TilePoint *path;
static int16 pathLength;
static StaticTilePoint baseCoords,
baseTileCoords,
centerPt, // The current center coordinates
bestLoc; // The best cell coordinates,
// currently visited
static uint8 centerPlatform,
bestPlatform;
static int16 fetchRadius;
static int32 firstTick,
lastTick,
timeLimit;
static DirMaskGroup *dirMasks;
// Calculates the center point given the base coordinate of the
// cell array and a queue item which contains cell coordinates.
static void calcCenterPt(const TilePoint &baseTileCoords_, const QueueItem &qi) {
centerPt.u = ((baseTileCoords_.u + qi.u) << kTileUVShift)
+ kTileUVSize / 2;
centerPt.v = ((baseTileCoords_.v + qi.v) << kTileUVShift)
+ kTileUVSize / 2;
centerPt.z = qi.z;
centerPlatform = qi.platform;
}
// Constructor
PathRequest(Actor *a, int16 howSmart);
public:
static PathTileRegion *tileArray;
virtual ~PathRequest() {
if (path)
delete[] path;
path = nullptr;
}
void requestAbort() {
flags |= aborted;
}
virtual void initialize();
virtual void finish(); // completion method
virtual void abortReq(); // abnormal termination method
PathResult findPath();
// Set and evaluate a new center location.
virtual bool setCenter(
const TilePoint &baseTileCoords,
const QueueItem &qi) = 0;
// Determine if path request will allow a move to the specified
// location
virtual bool validMove(const TilePoint &testPt) = 0;
// Virtual function for evaluating the cost of moving on stairs.
virtual int16 evaluateStairs(
const TilePoint &testPt,
Direction moveDir,
Direction stairDir,
int16 baseAltitute,
int16 upperAltitude) = 0;
// Virtual function for evaluating the cost of moving from the
// current center location to the specified coordinates.
virtual int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform) = 0;
// NEW added by Evan 12/3
virtual bool timeLimitExceeded();
};
/* ===================================================================== *
DestinationPathRequest Class
* ===================================================================== */
// This class is used to request a path leading to a specified
// destination.
class DestinationPathRequest : public PathRequest {
protected:
TilePoint destination; // The destination of the path
uint8 destPlatform; // The destination platform
// These static members are initialized when the path request
// becomes the current active request being serviced.
static StaticTilePoint targetCoords; // The current destination coordinates
// quantized to the nearest tile
// center.
static uint8 targetPlatform;
static int16 bestDist, // The distance from the target of
// the best cell visited so far.
centerCost; // The distance from the target of
// the current center coordinates.
public:
DestinationPathRequest(Actor *a, int16 howSmart);
// Initialize the static data members for this path request.
void initialize() override;
// Set and evaluate a new center location.
bool setCenter(
const TilePoint &baseTileCoords,
const QueueItem &qi) override;
bool validMove(const TilePoint &testPt) override;
// Evaluate the cost of travelling on these stairs
int16 evaluateStairs(
const TilePoint &testPt,
Direction moveDir,
Direction stairDir,
int16 baseAltitude,
int16 upperAltitude) override;
// Evaluate the cost of the specified move
int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform) override;
};
/* ===================================================================== *
WanderPathRequest Class
* ===================================================================== */
class WanderPathRequest : public PathRequest {
protected:
bool tethered; // Flag indicating if there is a
// tether on this path
// Tether coordinates
int16 tetherMinU,
tetherMinV,
tetherMaxU,
tetherMaxV;
// These static members are initialized when the path request
// becomes the current active request being serviced.
static StaticTilePoint startingCoords; // The actor's location at the
// beginning of the service.
static int16 bestDist, // The distance from the target of
// the best cell visited so far.
centerCost; // The distance from the target of
// the current center coordinates.
public:
// Constructor
WanderPathRequest(Actor *a, int16 howSmart);
// Initialize the static data members
void initialize() override;
// Set and evaluate a new center location.
bool setCenter(
const TilePoint &baseTileCoords,
const QueueItem &qi) override;
// Determine if point is within the tether region if there is a
// tether
bool validMove(const TilePoint &testPt) override;
// Evaluate the cost of moving on the specified stairs.
int16 evaluateStairs(
const TilePoint &testPt,
Direction moveDir,
Direction stairDir,
int16 baseAltitude,
int16 upperAltitude) override;
// Evaluate the cost of moving from the current center location
// to the specified location.
int16 evaluateMove(const TilePoint &testPt, uint8 testPlatform) override;
};
/* ===================================================================== *
Globals
* ===================================================================== */
PathRequest *currentRequest = nullptr;
static PathTilePosArray *pathTileArray;
static PathSubMetaFlags subMetaFlags;
static MaskComputer *maskComp;
static PriorityQueue<QueueItem, 192> *queue;
static PathArray *cellArray;
static TileRegion *objectVolumeArray;
struct VolumeLookupNode {
VolumeLookupNode *next;
TileRegion *volume;
};
static VolumeLookupNode volumeLookupNodePool[256];
static VolumeLookupNode *volumeLookupTable[searchDiameter][searchDiameter];
TilePoint *PathRequest::path = nullptr;
int16 PathRequest::pathLength;
StaticTilePoint PathRequest::baseCoords = {0, 0, 0},
PathRequest::baseTileCoords = {0, 0, 0},
PathRequest::centerPt = {0, 0, 0}, // The current center coordinates
PathRequest::bestLoc = {0, 0, 0}; // The best cell coordinates,
// currently visited
uint8 PathRequest::centerPlatform,
PathRequest::bestPlatform;
int16 PathRequest::fetchRadius;
int32 PathRequest::firstTick,
PathRequest::timeLimit;
DirMaskGroup *PathRequest::dirMasks = nullptr;
PathTileRegion *PathRequest::tileArray = nullptr;
StaticTilePoint DestinationPathRequest::targetCoords = {0, 0, 0};
uint8 DestinationPathRequest::targetPlatform;
int16 DestinationPathRequest::bestDist,
DestinationPathRequest::centerCost;
StaticTilePoint WanderPathRequest::startingCoords = {0, 0, 0};
int16 WanderPathRequest::bestDist,
WanderPathRequest::centerCost;
/* ===================================================================== *
PathQueue member functions
* ===================================================================== */
static void push(
const TilePoint &tp,
uint8 platform,
int cost,
int direction,
int8 platformDelta) {
assert(cellArray != nullptr);
PathCell *cellPtr;
bool newCell;
QueueItem newItem;
// Don't search beyond map edges
if (tp.u < 1 || tp.u >= searchDiameter - 1
|| tp.v < 1 || tp.v >= searchDiameter - 1)
return;
cellPtr = cellArray->makeCell(platform, tp.u, tp.v, &newCell);
assert(cellPtr != nullptr);
// If the cell is already visited, only
// update it if it was less cost to get here.
if (!newCell && cellPtr->cost <= cost) return;
newItem.u = tp.u; // coords of this point
newItem.v = tp.v; //
newItem.z = tp.z;
newItem.platform = platform;
newItem.cost = cost;
newItem.direction = direction;
newItem.pad = 0;
if (queue->insert(newItem)) {
cellPtr->direction = direction;
cellPtr->platformDelta = platformDelta;
cellPtr->cost = cost;
cellPtr->height = tp.z;
} else {
if (newCell)
cellArray->deleteCell(platform, tp.u, tp.v);
}
}
/* ===================================================================== *
Member Functions
* ===================================================================== */
/* ===================================================================== *
PathRequest Class
* ===================================================================== */
PathRequest::PathRequest(Actor *a, int16 howSmart) {
actor = a;
smartness = howSmart;
mTask = actor->_moveTask;
flags = mTask->flags & MotionTask::requestRun ? run : 0;
if (path == nullptr)
path = new TilePoint[kPathSize]();
mTask->pathFindTask = this;
}
void PathRequest::initialize() {
ProtoObj *proto = actor->proto();
TilePoint startingCoords = actor->getLocation();
int uCoord, vCoord;
int objectVolumes = 0;
int nextAvailableLookupNode = 0;
int curTileRegU,
curTileRegV,
minTileRegU,
minTileRegV,
maxTileRegU,
maxTileRegV;
uint8 pCross = proto->crossSection;
firstTick = gameTime,
timeLimit = /*flags & run ? ticksPerSecond / 4 :*/ ticksPerSecond;
fetchRadius =
((kTileUVSize / 2 + pCross) >> kTileUVShift) + 1;
dirMasks = maskComp->computeMask(pCross);
// Set the best location to the starting location
bestLoc.set(Nowhere.u, Nowhere.v, Nowhere.z);
// Calculate where search cells will be projected onto map
baseTileCoords.u = (startingCoords.u >> kTileUVShift) - searchCenter;
baseTileCoords.v = (startingCoords.v >> kTileUVShift) - searchCenter;
baseTileCoords.z = 0;
baseCoords.u = baseTileCoords.u << kTileUVShift;
baseCoords.v = baseTileCoords.v << kTileUVShift;
baseCoords.z = 0;
// Clear the priority queue
queue->clear();
// Initialize the tile array
tileArray->init(
actor->getMapNum(),
TilePoint(
baseTileCoords.u - 2,
baseTileCoords.v - 2,
0),
TilePoint(
searchDiameter + 4,
searchDiameter + 4,
0),
(PathTilePosInfo *)pathTileArray,
subMetaFlags);
for (uCoord = 0; uCoord < searchDiameter; uCoord++) {
for (vCoord = 0; vCoord < searchDiameter; vCoord++)
volumeLookupTable[uCoord][vCoord] = nullptr;
}
RegionalObjectIterator iter(
currentWorld,
baseCoords,
TilePoint(
baseCoords.u
+ (searchCenter << kTileUVShift) * 2,
baseCoords.v
+ (searchCenter << kTileUVShift) * 2,
0));
GameObject *obj = nullptr;
for (iter.first(&obj);
obj != nullptr;
iter.next(&obj)) {
TilePoint objLoc = obj->getLocation() - baseCoords;
ProtoObj *objProto = obj->proto();
TileRegion *objRegion = &objectVolumeArray[objectVolumes];
uint8 poCross = objProto->crossSection;
// Obviously, we shouldn't block ourselves.
if (obj == actor || obj->isInvisible()) continue;
// Dead corpses and invisible actors are not considered
// obstacles.
if (isActor(obj)
&& (((Actor *)obj)->isDead()
|| ((Actor *)obj)->hasEffect(actorInvisible)))
continue;
// Compute the objects volume
objRegion->min.u = objLoc.u - poCross;
objRegion->min.v = objLoc.v - poCross;
objRegion->min.z = objLoc.z;
objRegion->max.u = objLoc.u + poCross;
objRegion->max.v = objLoc.v + poCross;
objRegion->max.z = objLoc.z + objProto->height;
// Compute the tile region which this object overlays
minTileRegU = MAX(objRegion->min.u >> kTileUVShift, 0);
maxTileRegU = MIN(
(objRegion->max.u + kTileUVMask) >> kTileUVShift,
searchDiameter);
minTileRegV = MAX(objRegion->min.v >> kTileUVShift, 0);
maxTileRegV = MIN(
(objRegion->max.v + kTileUVMask) >> kTileUVShift,
searchDiameter);
for (curTileRegU = minTileRegU;
curTileRegU < maxTileRegU;
curTileRegU++) {
assert(curTileRegU >= 0 && curTileRegU < searchDiameter);
for (curTileRegV = minTileRegV;
curTileRegV < maxTileRegV;
curTileRegV++) {
assert(curTileRegV >= 0 && curTileRegV < searchDiameter);
VolumeLookupNode *node;
VolumeLookupNode **tablePtrPtr;
// Get the next lookup node
node = &volumeLookupNodePool[nextAvailableLookupNode++];
tablePtrPtr = &volumeLookupTable[curTileRegU][curTileRegV];
// Link into lookup table
node->volume = objRegion;
node->next = *tablePtrPtr;
*tablePtrPtr = node;
if (nextAvailableLookupNode
>= ARRAYSIZE(volumeLookupNodePool))
goto big_break;
}
}
if (++objectVolumes >= kObjectVolumeArraySize) break;
}
big_break:
// Compute the actor's tile region
minTileRegU = (startingCoords.u - pCross) >> kTileUVShift;
minTileRegV = (startingCoords.v - pCross) >> kTileUVShift;
maxTileRegU = (startingCoords.u + pCross + kTileUVMask)
>> kTileUVShift;
maxTileRegV = (startingCoords.v + pCross + kTileUVMask)
>> kTileUVShift;
for (curTileRegU = minTileRegU;
curTileRegU < maxTileRegU;
curTileRegU++) {
for (curTileRegV = minTileRegV;
curTileRegV < maxTileRegV;
curTileRegV++) {
TilePoint quantizedCoords,
offsetVector;
uint8 platform;
int16 dist,
zDist,
cost;
// Quantize this tile position to the tile center
quantizedCoords.u = (curTileRegU << kTileUVShift) + kTileUVSize / 2;
quantizedCoords.v = (curTileRegV << kTileUVShift) + kTileUVSize / 2;
quantizedCoords.z = startingCoords.z;
quantizedCoords.z = tileSlopeHeight(
quantizedCoords,
actor,
nullptr,
&platform);
// If the height difference is too great skip this tile
// position
if (ABS(quantizedCoords.z - startingCoords.z) > kMaxStepHeight)
continue;
// Compute initial cost based upon the distance from the
// starting location
offsetVector = quantizedCoords - startingCoords;
dist = offsetVector.quickHDistance();
zDist = ABS(offsetVector.z);
cost = dist + zDist;
// Push this point
push(
TilePoint(
curTileRegU - baseTileCoords.u,
curTileRegV - baseTileCoords.v,
quantizedCoords.z),
platform,
cost,
dirInvalid,
0);
}
}
}
void PathRequest::finish() {
Direction prevDir;
int16 prevHeight = 0;
TilePoint *resultSteps = path,
coords;
int16 stepCount = 0;
TilePoint *res;
PathCell *cell;
static TilePoint tempResult[32];
debugC(2, kDebugPath, "Finishing Path Request: %p", (void *)this);
if (bestLoc != Nowhere) {
cell = cellArray->getCell(bestPlatform, bestLoc.u, bestLoc.v);
assert(cell != nullptr);
if (cell->direction != dirInvalid) {
res = &tempResult[ARRAYSIZE(tempResult)];
prevDir = dirInvalid;
for (;;) {
int16 reverseDir;
cell = cellArray->getCell(bestPlatform, bestLoc.u, bestLoc.v);
assert(cell != nullptr);
if (cell->direction != dirInvalid) {
if (cell->direction != prevDir
|| ABS(cell->height - prevHeight) > kMaxStepHeight) {
if (res <= tempResult) break;
coords.u =
(bestLoc.u << kTileUVShift)
+ baseCoords.u
+ kTileUVSize / 2;
coords.v =
(bestLoc.v << kTileUVShift)
+ baseCoords.v
+ kTileUVSize / 2;
coords.z = cell->height;
*--res = coords;
prevDir = cell->direction;
prevHeight = cell->height;
}
reverseDir = (cell->direction + 4) & 0x07;
bestLoc += tDirTable2[reverseDir];
assert(bestLoc.u >= 0 && bestLoc.u < searchDiameter);
assert(bestLoc.v >= 0 && bestLoc.v < searchDiameter);
bestPlatform -= cell->platformDelta;
assert(bestPlatform < maxPlatforms);
} else
break;
}
if (resultSteps) {
while (stepCount < kPathSize
&& res < &tempResult[ARRAYSIZE(tempResult)]) {
*resultSteps++ = *res++;
stepCount++;
}
}
} else
// if pathfinder didn't get anywhere, we're done
flags |= completed;
}
pathLength = stepCount;
if (mTask->pathFindTask == this && mTask->isWalk()) {
memcpy(mTask->pathList, path, pathLength * sizeof path[0]);
mTask->pathCount = pathLength;
mTask->pathIndex = 0;
mTask->flags |= MotionTask::reset;
if (flags & completed) mTask->flags |= MotionTask::finalPath;
mTask->pathFindTask = nullptr;
}
}
void PathRequest::abortReq() {
debugC(4, kDebugPath, "Aborting Path Request: %p", (void *)this);
if (mTask->pathFindTask == this)
mTask->pathFindTask = nullptr;
}
PathResult PathRequest::findPath() {
assert(cellArray != nullptr);
static const uint8 costTable[] = {4, 10, 12, 16, 12, 10, 4, 0, 4, 10, 12, 16, 12, 10, 4, 0};
ProtoObj *proto = actor->proto();
QueueItem qi;
uint8 pCross = proto->crossSection;
debugC(4, kDebugPath, "Finding Path for %p: pCross = %d", (void *)this, pCross);
if (flags & aborted) return pathAborted;
int32 lastTick_ = gameTime;
while (queue->remove(qi)) {
assert(cellArray->getCell(qi.platform, qi.u, qi.v) != nullptr);
assert(qi.u >= 1 && qi.u < searchDiameter - 1);
assert(qi.v >= 1 && qi.v < searchDiameter - 1);
TilePoint centerTileCoords;
StaticTilePoint *tDir;
int32 i,
dir,
endDir;
// Limit the total path length to avoid maze-solving
if (qi.cost > smartness) continue;
// Set a new center and determine if we're done
if (setCenter(baseTileCoords, qi)) return pathDone;
// Calculate the coordinates of the center in tiles
centerTileCoords.u = qi.u + baseTileCoords.u;
centerTileCoords.v = qi.v + baseTileCoords.v;
centerTileCoords.z = 0;
if (qi.direction == dirInvalid) {
// If this is the starting position, check all directions
i = dir = 0;
tDir = tDirTable2;
endDir = 8;
tileArray->fetchTileSection(
TilePoint(qi.u - fetchRadius, qi.v - fetchRadius, 0)
+ baseTileCoords,
TilePoint(
(fetchRadius << 1) + 1,
(fetchRadius << 1) + 1,
0));
} else {
// Check only the forward directions
i = dir = (qi.direction + 6) & 0x7;
tDir = &tDirTable2[dir];
endDir = i + 5;
switch (qi.direction) {
case 0:
tileArray->fetchTileSection(
TilePoint(
qi.u + fetchRadius,
qi.v - fetchRadius,
0)
+ baseTileCoords,
TilePoint(1, fetchRadius << 1, 0));
// fall through
case 1:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius,
qi.v + fetchRadius,
0)
+ baseTileCoords,
TilePoint((fetchRadius << 1) + 1, 1, 0));
break;
case 2:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius + 1,
qi.v + fetchRadius,
0)
+ baseTileCoords,
TilePoint(fetchRadius << 1, 1, 0));
// fall through
case 3:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius,
qi.v - fetchRadius,
0)
+ baseTileCoords,
TilePoint(1, (fetchRadius << 1) + 1, 0));
break;
case 4:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius,
qi.v - fetchRadius + 1,
0)
+ baseTileCoords,
TilePoint(1, fetchRadius << 1, 0));
// fall through
case 5:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius,
qi.v - fetchRadius,
0)
+ baseTileCoords,
TilePoint((fetchRadius << 1) + 1, 1, 0));
break;
case 6:
tileArray->fetchTileSection(
TilePoint(
qi.u - fetchRadius,
qi.v - fetchRadius,
0)
+ baseTileCoords,
TilePoint(fetchRadius << 1, 1, 0));
// fall through
case 7:
tileArray->fetchTileSection(
TilePoint(
qi.u + fetchRadius,
qi.v - fetchRadius,
0)
+ baseTileCoords,
TilePoint(1, (fetchRadius << 1) + 1, 0));
}
}
for (;
i < endDir;
tDir = &tDirTable2[(dir = (++i & 0x7))]) {
if (!validMove(centerPt + tDirTable3[dir]))
continue;
PathTileInfo pti;
TilePoint testPt = centerPt;
uint8 testPlatform;
uint32 terrain = 0;
int32 cost;
DirMask &dMask = (*dirMasks)[dir];
int16 prevZ = centerPt.z;
for (int d = 0; d < 4; d++) {
int u, v;
uint8 maskU, maskV;
PointMask &ptMask = dMask[d];
TileRegion maskReg,
actorVolume;
maskReg.min.u = centerTileCoords.u + ptMask.offset.u;
maskReg.max.u = maskReg.min.u + ptMask.size.u;
maskReg.min.v = centerTileCoords.v + ptMask.offset.v;
maskReg.max.v = maskReg.min.v + ptMask.size.v;
testPt.u += tDirTable[dir].u;
testPt.v += tDirTable[dir].v;
testPt.z = tileSlopeHeight(
*tileArray,
testPt,
actor,
&pti,
&testPlatform);
// Determine if elevation change is too great
if (ABS(testPt.z - prevZ) <= kMaxStepHeight)
prevZ = testPt.z;
else
goto big_continue;
// Compute the actor's volume at this point relative to
// the base of the search region
actorVolume.min.u = testPt.u
- baseCoords.u
- pCross;
actorVolume.max.u = actorVolume.min.u + pCross * 2;
actorVolume.min.v = testPt.v
- baseCoords.v
- pCross;
actorVolume.max.v = actorVolume.min.v + pCross * 2;
actorVolume.min.z = testPt.z;
actorVolume.max.z = testPt.z + proto->height;
int16 aph = actor->proto()->height;
for (u = maskReg.min.u, maskU = 0;
u < maskReg.max.u;
u++, maskU++) {
PathTilePosInfo *arrRow =
&tileArray->array[
(u - tileArray->origin.u)
* tileArray->area.v];
for (v = maskReg.min.v, maskV = 0;
v < maskReg.max.v;
v++, maskV++) {
VolumeLookupNode *node;
// Lookup any potentially intersecting object
// volumes
for (node = volumeLookupTable
[u - baseTileCoords.u]
[v - baseTileCoords.v];
node != nullptr;
node = node->next) {
TileRegion *trv = node->volume;
// Check for volume intersection
if (trv->min.u < actorVolume.max.u
&& actorVolume.min.u < trv->max.u
&& trv->min.v < actorVolume.max.v
&& actorVolume.min.v < trv->max.v
&& trv->min.z < actorVolume.max.z
&& actorVolume.min.z + kMaxStepHeight
< trv->max.z)
goto big_continue;
}
terrain |= tileTerrain(
&arrRow[v - tileArray->origin.v],
ptMask.mask[(maskU << 2) | maskV],
testPt.z,
testPt.z + aph);
}
}
}
if (terrain & (terrainImpassable | terrainRaised)) continue;
// determine the height of the center of the tile
// that we're testing.
// Assign costs based on the direction of travel
if (terrain & terrainSlow) {
cost = dir & 1 ? straightHardCost : diagHardCost;
} else if (terrain & terrainAverage) {
cost = dir & 1 ? straightNormalCost : diagNormalCost;
} else {
cost = dir & 1 ? straightEasyCost : diagEasyCost;
}
// We must treat stairs as a special case
if (pti.surfaceTile != nullptr
&& (pti.surfaceTile->combinedTerrainMask() & terrainStair)) {
uint8 *cornerHeight = pti.surfaceTile->attrs.cornerHeight;
uint8 stairDir;
int16 stairHeight;
// Determine the direction and upper altitude of the
// stairs
if (*((uint16 *)&cornerHeight[0]) == 0) {
stairDir = 1;
stairHeight = pti.surfaceHeight + cornerHeight[2];
} else if (*((uint16 *)&cornerHeight[1]) == 0) {
stairDir = 3;
stairHeight = pti.surfaceHeight + cornerHeight[3];
} else if (*((uint16 *)&cornerHeight[2]) == 0) {
stairDir = 5;
stairHeight = pti.surfaceHeight + cornerHeight[0];
} else if (cornerHeight[0] == 0 && cornerHeight[3] == 0) {
stairDir = 7;
stairHeight = pti.surfaceHeight + cornerHeight[1];
} else
continue;
// Do not go onto the stair at a right angle
if (stairDir == 1 || stairDir == 5) {
if (dir == 3 || dir == 7)
continue;
} else if (stairDir == 3 || stairDir == 7) {
if (dir == 1 || dir == 5)
continue;
}
// Add any additional costs for travelling on these
// stairs.
cost += evaluateStairs(
testPt,
dir,
stairDir,
pti.surfaceHeight,
stairHeight);
}
// Assign additional costs based on having to travel
// uphill or having to jump down steep slopes
else if (testPt.z > centerPt.z
|| testPt.z < centerPt.z - 12) {
cost += ((testPt.z - centerPt.z)
* (testPt.z - centerPt.z)) >> 5;
}
// If the drop-off is too much, then don't go there!
// (i.e. don't jmup off of cliffs). Also we can
// only climb steps below a certain height.
// if ( testPt.z < centerPt.z - kMaxJumpStep
// || testPt.z > centerPt.z + kMaxStepHeight)
// continue;
// Turns are expensive, the sharper turns are more so.
cost += costTable[
7
+ dir
- (qi.direction != dirInvalid
? qi.direction
: actor->_currentFacing)];
#if VISUAL1
TPLine(centerPt, testPt);
#endif
// Determine the final cost of moving to this cell
cost = clamp(0L,
(int32)qi.cost
+ (int32)cost
+ (int32)evaluateMove(testPt, testPlatform),
(int32)maxint16);
// Cost should never be less than previous cost
cost = MAX<int16>(cost, qi.cost);
// Push the new point onto the queue->
push(
TilePoint(
qi.u + tDir->u,
qi.v + tDir->v,
testPt.z),
testPlatform,
cost,
dir,
testPlatform - centerPlatform);
assert(cellArray->getCell(centerPlatform, qi.u, qi.v) != nullptr);
big_continue:
;
}
if ((gameTime - lastTick_) >= 4) { // JEFFKLUDGE
if (timeLimitExceeded())
return pathDone;
}
}
return pathDone;
}
//-------------------------------------------------------------------
// Path finder time management:
// Originally all requests had a time limit of 72 and IQ of 400
// I have rewired it to use an IQ of 400 for player actors, and
// 100 for NPCs. That IQ also affects the time limit now. Players
// will generally get (72/2)+(400/10) or 76 ticks. NPCs will
// usually get (72/2)+(100/10) or 46 ticks.
bool PathRequest::timeLimitExceeded() {
#ifdef OLD_PATHFINDER_TIME_MGMT
return (gameTime - firstTick >= timeLimit);
#else
int32 cutoff = smartness / (queue->getCount() ? 5 : 8);
return (gameTime - firstTick >= cutoff);
#endif
}
/* ===================================================================== *
DestinationPathRequest Class
* ===================================================================== */
DestinationPathRequest::DestinationPathRequest(Actor *a, int16 howSmart) :
PathRequest(a, howSmart) {
// Quantize the target destination to the nearest tile center.
mTask->finalTarget.u = (mTask->finalTarget.u & ~kTileUVMask) + kTileUVSize / 2;
mTask->finalTarget.v = (mTask->finalTarget.v & ~kTileUVMask) + kTileUVSize / 2;
mTask->finalTarget.z = tileSlopeHeight(
mTask->finalTarget,
a,
nullptr,
&destPlatform);
destination = mTask->finalTarget;
}
// Initialize the static data members
void DestinationPathRequest::initialize() {
debugC(2, kDebugPath, "Initializing Path Request: %p", (void *)this);
PathRequest::initialize();
// Initialize bestDist to the highest possible value.
bestDist = maxint16;
// Quantize the target coordinates to the nearest tile center.
targetCoords.u = (destination.u & ~kTileUVMask) + kTileUVSize / 2;
targetCoords.v = (destination.v & ~kTileUVMask) + kTileUVSize / 2;
targetCoords.z = destination.z;
targetPlatform = destPlatform;
}
// Set and evaluate a new center location.
bool DestinationPathRequest::setCenter(
const TilePoint &baseTileCoords_,
const QueueItem &qi) {
int16 dist,
zDist,
platDiff;
TilePoint targetDelta;
// Calculate the center coordinates.
calcCenterPt(baseTileCoords_, qi);
// Determine the target vector in order to calculate distance.
targetDelta = (targetCoords - centerPt);
dist = targetDelta.quickHDistance();
zDist = ABS(targetDelta.z);
platDiff = ABS(centerPlatform - targetPlatform);
centerCost = dist + zDist * (platDiff + 1);
// Determine if this location is closer than any location we have
// previously visited.
if (centerCost < bestDist) {
// Save closest point encountered.
bestLoc.u = qi.u;
bestLoc.v = qi.v;
bestLoc.z = qi.z;
bestPlatform = qi.platform;
bestDist = centerCost;
// If we're at target square, then we're done!
if (dist == 0 && zDist <= kMaxStepHeight) {
flags |= PathRequest::completed;
// Return true to indicate that the path finding is done.
return true;
}
}
return false;
}
bool DestinationPathRequest::validMove(const TilePoint &) {
return true;
}
// Evaluate the cost of moving on the specified stairs in the specified
// direction
int16 DestinationPathRequest::evaluateStairs(
const TilePoint &testPt, // Coordinates of location we are testing
Direction moveDir, // Direction of movement
Direction stairDir, // Direction of up stairs
int16 baseAltitude, // Altitude of bottom of the stairs
int16 upperAltitude) { // Altitude of the top of the stairs
int16 cost = 0;
// Determine if the stairs are going towards the
// altitude of the target coordinates. If not, assign
// additional costs.
if (targetCoords.z >= upperAltitude) {
if (moveDir != stairDir) {
cost = ((testPt.z - centerPt.z)
* (testPt.z - centerPt.z)) >> 4;
}
} else if (targetCoords.z <= baseAltitude) {
if (moveDir == stairDir) {
cost = ((testPt.z - centerPt.z)
* (testPt.z - centerPt.z)) >> 4;
}
}
return cost;
}
// Evaluate the cost of moving to the specified point from the
// current center location.
int16 DestinationPathRequest::evaluateMove(
const TilePoint &testPt,
uint8 testPlatform) {
int16 dist,
zDist,
platDiff;
TilePoint targetDelta;
// Determine the target vector of the specified coordinates, in
// order to calculate the distance.
targetDelta = targetCoords - testPt;
dist = targetDelta.quickHDistance();
zDist = ABS(targetDelta.z);
platDiff = ABS(testPlatform - targetPlatform);
return (dist + zDist * (platDiff + 1) - centerCost) >> 2;
}
/* ===================================================================== *
WanderPathRequest Class
* ===================================================================== */
WanderPathRequest::WanderPathRequest(
Actor *a,
int16 howSmart) :
PathRequest(a, howSmart) {
if (mTask->flags & MotionTask::tethered) {
tethered = true;
tetherMinU = mTask->tetherMinU;
tetherMinV = mTask->tetherMinV;
tetherMaxU = mTask->tetherMaxU;
tetherMaxV = mTask->tetherMaxV;
} else {
tethered = false;
tetherMinU = tetherMinV = tetherMaxU = tetherMaxV = 0;
}
}
// Initialize the static data members
void WanderPathRequest::initialize() {
PathRequest::initialize();
// Initialize bestDist to zero.
bestDist = 0;
startingCoords.set(actor->getLocation().u,
actor->getLocation().v,
actor->getLocation().z);
}
// Set and evaluate a new center location.
bool WanderPathRequest::setCenter(
const TilePoint &baseTileCoords_,
const QueueItem &qi) {
int16 dist,
zDist;
TilePoint movementDelta;
// Calculate the center coordinates.
calcCenterPt(baseTileCoords_, qi);
// Determine the movement vector in order to calculate distance.
movementDelta = (startingCoords - centerPt);
dist = movementDelta.quickHDistance();
zDist = ABS(movementDelta.z);
centerCost = dist + zDist;
// Determine if this location is farther than any location we have
// previously visited.
if (centerCost > bestDist) {
// Save farthest point encountered.
bestLoc.u = qi.u;
bestLoc.v = qi.v;
bestLoc.z = qi.z;
bestPlatform = qi.platform;
bestDist = centerCost;
}
return false;
}
bool WanderPathRequest::validMove(const TilePoint &testPt) {
return !tethered
|| (testPt.u >= tetherMinU
&& testPt.u < tetherMaxU
&& testPt.v >= tetherMinV
&& testPt.v < tetherMaxV);
}
// There will be no additional costs for travelling on stairs
int16 WanderPathRequest::evaluateStairs(
const TilePoint &,
Direction,
Direction,
int16,
int16) {
return 0;
}
// Evaluate the cost of moving to the specified point from the
// current center location.
int16 WanderPathRequest::evaluateMove(const TilePoint &testPt, uint8) {
int16 dist,
zDist;
TilePoint movementDelta;
// Determine the movement vector of the specified coordinates, in
// order to calculate the distance.
movementDelta = startingCoords - testPt;
dist = movementDelta.quickHDistance();
zDist = ABS(movementDelta.z) >> 1;
return (centerCost - (dist + zDist)) >> 1;
}
void runPathFinder() {
if (currentRequest == nullptr && !g_vm->_pathQueue.empty()) {
currentRequest = g_vm->_pathQueue.front();
g_vm->_pathQueue.pop_front();
currentRequest->initialize();
}
if (currentRequest != nullptr) {
PathResult result;
result = currentRequest->findPath();
if (result != pathNotDone) {
if (result == pathDone)
currentRequest->finish();
else
currentRequest->abortReq();
delete currentRequest;
currentRequest = nullptr;
cellArray->reset();
}
}
}
void addPathRequestToQueue(PathRequest *pr) {
Actor *a = pr->actor;
Actor *centerActor = getCenterActor();
if (a == centerActor)
g_vm->_pathQueue.push_front(pr);
else {
if (isPlayerActor(a)) {
Common::List<PathRequest *>::iterator it;
for (it = g_vm->_pathQueue.begin(); it != g_vm->_pathQueue.end(); ++it) {
Actor *prActor = (*it)->actor;
if (prActor != centerActor || !isPlayerActor(prActor))
break;
}
if (it != g_vm->_pathQueue.end())
g_vm->_pathQueue.insert(it, pr);
else
g_vm->_pathQueue.push_back(pr);
} else
g_vm->_pathQueue.push_back(pr);
}
}
void RequestPath(MotionTask *mTask, int16 smartness) {
DestinationPathRequest *pr;
Actor *a = (Actor *)mTask->object;
if ((pr = new DestinationPathRequest(a, smartness)) != nullptr)
addPathRequestToQueue(pr);
}
void RequestWanderPath(MotionTask *mTask, int16 smartness) {
WanderPathRequest *pr;
Actor *a = (Actor *)mTask->object;
if ((pr = new WanderPathRequest(a, smartness)) != nullptr)
addPathRequestToQueue(pr);
}
void abortPathFind(MotionTask *mTask) {
if (mTask->pathFindTask) {
PathRequest *pr = mTask->pathFindTask;
if (pr == currentRequest)
pr->requestAbort();
else
g_vm->_pathQueue.remove(pr);
mTask->pathFindTask = nullptr;
}
}
/* ===================================================================== *
A special pathfinder to select a random site reachable from another one.
* ===================================================================== */
/* Specs:
1. Find a locations which is reachable from 'center', and which
is between minDist and maxDist tiles away (preferably midway
between them).
2. Terrain cost and direction of travel need not be considered.
3. Don't put creatures on top of houses, but do put them
on top of hills.
4. To simplify even further, checking in diagonal directions
is not needed.
5. This routine should probably run synchronously, and therefore
cannot share the data structures of the real pathfinder.
*/
enum cellStates {
cellUnvisited = 0,
cellOccupied = (1 << 0),
cellVisited = (1 << 1)
};
typedef uint8 SimpleCellArray[searchDiameter][searchDiameter];
static PriorityQueue<QueueItem, 128> *squeue;
static void spush(const TilePoint &tp, int cost, int direction) {
QueueItem newItem;
// Don't search beyond map edges
if (tp.u < 1 || tp.u >= searchDiameter - 1
|| tp.v < 1 || tp.v >= searchDiameter - 1)
return;
newItem.u = tp.u; // coords of this point
newItem.v = tp.v; //
newItem.z = tp.z;
newItem.cost = cost;
newItem.direction = direction;
newItem.platform = 0;
squeue->insert(newItem);
}
/* ordering of bits:
u3v3 u3v2 u3v1 u3v0 +U
u2v3 u2v2 u2v1 u2v0 +V <---> -V |
u1v3 u1v2 u1v1 u1v0 |
u0v3 u0v2 u0v1 u0v0 -U
15 14 13 12 11 10 09 08
---- ---- ---- ---- ---- ---- ---- ----
u3v3 u3v2 u3v1 u3v0 u2v3 u2v2 u2v1 u2v0
07 06 05 04 03 02 01 00
---- ---- ---- ---- ---- ---- ---- ----
u1v3 u1v2 u1v1 u1v0 u0v3 u0v2 u0v1 u0v0
*/
/* 0000 0110 0000 0000 <--transition masks
0110 0110 0111 1110
0110 0110 0111 1110
0110 0000 0000 0000
-U +U -V +V
0666 6660 0770 0ee0
*/
static const uint16
posUMask = 0x6660,
negUMask = 0x0666,
posVMask = 0x0770,
negVMask = 0x0ee0;
uint16 sTerrainMasks[8] = {
posUMask, negUMask, // dirUpLeft (U+)
negVMask, posVMask, // dirDownLeft (V-)
negUMask, posUMask, // dirDownRight (U-)
posVMask, negVMask, // dirUpRight (V+)
};
TilePoint selectNearbySite(
ObjectID worldID,
const TilePoint &startingCoords,
int32 minDist,
int32 maxDist,
bool offScreenOnly) { // true if we want it off-screen
assert(isWorld(worldID));
TilePoint baseCoords,
baseTileCoords,
centerPt, // The current center coordinates
bestLoc; // The best cell coordinates,
// currently visited
int16 mapNum = GameWorld::IDtoMapNum(worldID);
int32 bestRating = -100,
bestPossible = (maxDist - minDist) / 2;
QueueItem qi;
// Allocate the array of cells
SimpleCellArray *cellArray1 = (SimpleCellArray *)malloc(sizeof * cellArray1);
// Nowhere indicates failure of the algorithm.
bestLoc = Nowhere;
// Calculate where search cells will be projected onto map
baseTileCoords.u = (startingCoords.u >> kTileUVShift) - searchCenter;
baseTileCoords.v = (startingCoords.v >> kTileUVShift) - searchCenter;
baseTileCoords.z = 0;
baseCoords.u = baseTileCoords.u << kTileUVShift;
baseCoords.v = baseTileCoords.v << kTileUVShift;
baseCoords.z = 0;
// Clear the search array and the queue
memset(cellArray1, cellUnvisited, sizeof(*cellArray1));
squeue->clear();
// Iterate through all actors in the region and mark areas
// as occupied.
RegionalObjectIterator iter(
(GameWorld *)GameObject::objectAddress(worldID),
baseCoords,
TilePoint(
baseCoords.u
+ (searchCenter << kTileUVShift) * 2,
baseCoords.v
+ (searchCenter << kTileUVShift) * 2,
0));
GameObject *obj = nullptr;
for (iter.first(&obj);
obj != nullptr;
iter.next(&obj)) {
TilePoint objLoc = obj->getLocation();
ProtoObj *objProto = obj->proto();
// If the object is higher than the actor's head, or
// low enough to step over, then ignore it.
if (objLoc.z >= startingCoords.z + 80
|| objLoc.z + objProto->height <= startingCoords.z + kMaxStepHeight / 2) {
continue;
}
// Calculate which tile actor is standing on.
objLoc = (objLoc - baseCoords) >> kTileUVShift;
// If that tile is in the search area, then mark it.
if (objLoc.u >= 0 && objLoc.u < searchDiameter
&& objLoc.v >= 0 && objLoc.v < searchDiameter) {
(*cellArray1)[objLoc.u][objLoc.v] = cellOccupied;
}
}
// Push the starting location in the center of the array.
spush(TilePoint(searchCenter, searchCenter, startingCoords.z),
1, // initial cost is 1
0); // facing irrelevant
while (squeue->remove(qi)) {
TilePoint centerTileCoords,
distVector;
StaticTilePoint *tDir;
int16 dir;
int32 distFromCenter,
rating;
// Calculate the distance between the current point and
// the center of the search
distFromCenter =
quickDistance(
Point32(qi.u - searchCenter, qi.v - searchCenter));
// max( ABS( qi.u - searchCenter ), ABS( qi.v - searchCenter ) );
// Calculate the "goodness" of this cell -- is it in the
// middle of the band between min and max?
rating = MIN(distFromCenter - minDist, maxDist - distFromCenter);
// Calculate the coordinates of the center in tiles
centerTileCoords.u = qi.u + baseTileCoords.u;
centerTileCoords.v = qi.v + baseTileCoords.v;
centerTileCoords.z = 0;
centerPt.u = (centerTileCoords.u << kTileUVShift) + kTileUVSize / 2;
centerPt.v = (centerTileCoords.v << kTileUVShift) + kTileUVSize / 2;
centerPt.z = qi.z;
// If this is the best cell found so far, and it is not
// occupied, then mark it as the best cell.
if (rating > bestRating
&& !((*cellArray1)[qi.u][qi.v] & cellOccupied)) {
bool cellOK = true;
// if this point is on-screen, we might want to reject it...
if (offScreenOnly) {
Point16 screenCoords; // screen coordinates
// Convert to XY coords.
TileToScreenCoords(centerPt, screenCoords);
// If the point is on-screen, then reject it.
// (We want the monsters to walk in from off-screen,
// not 'pop in').
if (screenCoords.x >= -16 && screenCoords.x <= kTileRectWidth + 16
&& screenCoords.y >= -16 && screenCoords.y <= kTileRectHeight + 80) {
cellOK = false;
}
}
// Save closest point encountered.
if (cellOK) {
bestLoc.u = qi.u;
bestLoc.v = qi.v;
bestLoc.z = qi.z;
bestRating = rating;
if (rating >= bestPossible) break;
}
}
if (distFromCenter >= maxDist) continue;
for (dir = dirUpLeft;
dir <= dirUpRight;
dir += 2) {
uint32 terrain;
uint8 *cell;
TilePoint testPt;
StandingTileInfo sti;
TilePoint fromSubPt,
toSubPt;
bool traversable = true;
int16 i;
uint16 *moveMask = &sTerrainMasks[dir - 1];
tDir = &tDirTable2[dir];
cell = &(*cellArray1)[qi.u + tDir->u][qi.v + tDir->v];
// Only visit each cell once. Do this before terrain
// is checked, to save time.
if (*cell & cellVisited) continue;
testPt = centerPt + tDirTable3[dir];
// Get info about the terrain at that point
terrain = tileTerrain(mapNum,
centerTileCoords,
moveMask[0],
centerPt.z + 8,
centerPt.z + 68);
// Reject if we can't move
if (terrain & (terrainImpassable | terrainRaised)) {
// But only if this is isn't the center point
if (distFromCenter > 0) continue;
}
// Get the height of the terrain at the new point
testPt.z = tileSlopeHeight(testPt, mapNum, 68, &sti);
// If it's too high to step, then don't continue
// if (testPt.z - qi.z > kMaxStepHeight) continue;
fromSubPt = centerPt;
for (i = 0; i < kTileSubSize; i++) {
int16 deltaZ;
// Next sub tile
toSubPt = fromSubPt + tDirTable[dir];
toSubPt.z = tileSlopeHeight(toSubPt, mapNum, 68);
deltaZ = toSubPt.z - fromSubPt.z;
// If it's too high to step, then don't continue
if (deltaZ > kMaxStepHeight || deltaZ < -(kMaxStepHeight * 2)) {
traversable = false;
break;
}
fromSubPt = toSubPt;
}
if (!traversable) continue;
// Get info about terrain at new point
terrain = tileTerrain(mapNum,
centerTileCoords + *tDir,
moveMask[1],
testPt.z + 8,
testPt.z + 68);
// Reject if terrain allows no entry
if (terrain & (terrainImpassable | terrainRaised)) continue;
#if VISUAL6
TPLine(centerPt, testPt);
#endif
*cell |= cellVisited;
// Push the new point onto the queue->
// (Cost is random so as to allow site selection to
// be somewhat non-deterministic).
spush(TilePoint(qi.u + tDir->u,
qi.v + tDir->v,
testPt.z),
qi.cost + g_vm->_rnd->getRandomNumber(3),
dir);
}
}
free(cellArray1);
return bestLoc != Nowhere
? TilePoint(
((bestLoc.u + baseTileCoords.u) << kTileUVShift) + kTileUVSize / 2,
((bestLoc.v + baseTileCoords.v) << kTileUVShift) + kTileUVSize / 2,
bestLoc.z)
: Nowhere;
}
bool checkPath(
ObjectID worldID,
uint8 height,
const TilePoint &startingPt,
const TilePoint &destPt) {
TilePoint startingCoords = startingPt,
destCoords = destPt,
startingTileCoords,
destTileCoords;
TilePoint baseCoords,
baseTileCoords,
centerPt; // The current center coordinates
int minTileRegU,
minTileRegV,
maxTileRegU,
maxTileRegV,
curTileRegU,
curTileRegV;
int16 mapNum = GameWorld::IDtoMapNum(worldID);
QueueItem qi;
StandingTileInfo sti;
startingTileCoords.u = startingCoords.u >> kTileUVShift;
startingTileCoords.v = startingCoords.v >> kTileUVShift;
startingTileCoords.z = 0;
destTileCoords.u = destCoords.u >> kTileUVShift;
destTileCoords.v = destCoords.v >> kTileUVShift;
destTileCoords.z = 0;
// Quantize destination coords to nearest tile center
destCoords.u = (destTileCoords.u << kTileUVShift) + kTileUVSize / 2;
destCoords.v = (destTileCoords.v << kTileUVShift) + kTileUVSize / 2;
destCoords.z = tileSlopeHeight(destCoords, mapNum, height);
// Determine if destination is outside the search region
if (destTileCoords.u < startingTileCoords.u - searchCenter
|| destTileCoords.u >= startingTileCoords.u + searchCenter
|| destTileCoords.v < startingTileCoords.v - searchCenter
|| destTileCoords.v >= startingTileCoords.v + searchCenter)
return false;
// Allocate the array of cells
SimpleCellArray *cellArray1 = (SimpleCellArray *)malloc(sizeof(*cellArray1));
if (cellArray1 == nullptr)
return false;
// Calculate where search cells will be projected onto map
baseTileCoords.u = startingTileCoords.u - searchCenter;
baseTileCoords.v = startingTileCoords.v - searchCenter;
baseTileCoords.z = 0;
baseCoords.u = baseTileCoords.u << kTileUVShift;
baseCoords.v = baseTileCoords.v << kTileUVShift;
baseCoords.z = 0;
// Clear the search array and the queue
memset(cellArray1, cellUnvisited, sizeof(* cellArray1));
squeue->clear();
// Push the starting location in the center of the array.
minTileRegU = (startingCoords.u - kTileUVSize / 2) >> kTileUVShift;
minTileRegV = (startingCoords.v - kTileUVSize / 2) >> kTileUVShift;
maxTileRegU = (startingCoords.u + kTileUVSize / 2 + kTileUVMask)
>> kTileUVShift;
maxTileRegV = (startingCoords.v + kTileUVSize / 2 + kTileUVMask)
>> kTileUVShift;
for (curTileRegU = minTileRegU;
curTileRegU < maxTileRegU;
curTileRegU++) {
for (curTileRegV = minTileRegV;
curTileRegV < maxTileRegV;
curTileRegV++) {
TilePoint quantizedCoords,
offsetVector;
int16 dist,
zDist,
cost;
// Quantize this tile position to the tile center
quantizedCoords.u = (curTileRegU << kTileUVShift) + kTileUVSize / 2;
quantizedCoords.v = (curTileRegV << kTileUVShift) + kTileUVSize / 2;
quantizedCoords.z = startingCoords.z;
quantizedCoords.z = tileSlopeHeight(quantizedCoords, mapNum, height);
// If the height difference is too great skip this tile
// position
if (ABS(quantizedCoords.z - startingCoords.z) > kMaxStepHeight)
continue;
// Compute initial cost based upon the distance from the
// starting location
offsetVector = quantizedCoords - startingCoords;
dist = offsetVector.quickHDistance();
zDist = ABS(offsetVector.z);
cost = dist + zDist;
// Push this point
spush(
TilePoint(
curTileRegU - baseTileCoords.u,
curTileRegV - baseTileCoords.v,
quantizedCoords.z),
cost + 1,
0);
}
}
while (squeue->remove(qi)) {
TilePoint centerTileCoords;
StaticTilePoint *tDir;
int16 centerDistFromDest;
int dir;
// Calculate the coordinates of the center in tiles
centerTileCoords.u = qi.u + baseTileCoords.u;
centerTileCoords.v = qi.v + baseTileCoords.v;
centerTileCoords.z = 0;
centerPt.u = (centerTileCoords.u << kTileUVShift) + kTileUVSize / 2;
centerPt.v = (centerTileCoords.v << kTileUVShift) + kTileUVSize / 2;
centerPt.z = qi.z;
centerDistFromDest = (centerPt - destCoords).quickHDistance();
for (dir = dirUpLeft;
dir <= dirUpRight;
dir += 2) {
uint32 terrain;
uint8 *cell;
TilePoint testPt,
testTileCoords,
fromSubPt,
toSubPt;
int16 testDistFromDest,
deltaDistFromDest;
int i;
bool traversable = true;
uint16 *moveMask = &sTerrainMasks[dir - 1];
tDir = &tDirTable2[dir];
testTileCoords.u = centerTileCoords.u + tDir->u;
testTileCoords.v = centerTileCoords.v + tDir->v;
testTileCoords.z = 0;
cell = &(*cellArray1)[qi.u + tDir->u][qi.v + tDir->v];
// Only visit each cell once..
if (*cell & cellVisited) continue;
testPt = centerPt + tDirTable3[dir];
testDistFromDest = (testPt - destCoords).quickHDistance();
deltaDistFromDest = testDistFromDest - centerDistFromDest;
// Get info about the terrain at that point
terrain = tileTerrain(mapNum,
centerTileCoords,
moveMask[0],
centerPt.z + 8,
centerPt.z + height);
// Reject if we can't move
if (terrain & (terrainImpassable | terrainRaised)) continue;
// Get the height of the terrain at the new point
testPt.z = tileSlopeHeight(testPt, mapNum, height, &sti);
fromSubPt = centerPt;
for (i = 0; i < kTileSubSize; i++) {
int16 deltaZ;
// Next sub tile
toSubPt = fromSubPt + tDirTable[dir];
toSubPt.z = tileSlopeHeight(toSubPt, mapNum, height);
deltaZ = toSubPt.z - fromSubPt.z;
// If it's too high to step, then don't continue
if (deltaZ > kMaxStepHeight || deltaZ < -(kMaxStepHeight * 2)) {
traversable = false;
break;
}
fromSubPt = toSubPt;
}
if (!traversable) continue;
// Get info about terrain at new point
terrain = tileTerrain(mapNum,
centerTileCoords + *tDir,
moveMask[1],
testPt.z + 8,
testPt.z + height);
// Reject if terrain allows no entry
if (terrain & (terrainImpassable | terrainRaised)) continue;
#if VISUAL7
TPLine(centerPt, testPt);
#endif
*cell |= cellVisited;
// If we're there, we're done
if (testTileCoords == destTileCoords) {
free(cellArray1);
// If the resulting height is significantly different
// from the destination height, assume we're on a
// different level and return false.
return ABS(testPt.z - destCoords.z) <= kMaxStepHeight;
}
// Push the new point onto the queue->
spush(TilePoint(qi.u + tDir->u,
qi.v + tDir->v,
testPt.z),
qi.cost + (deltaDistFromDest + kTileUVSize) / 4,
dir);
}
}
free(cellArray1);
// If we're here we've haven't found a path
return false;
}
/* ===================================================================== *
Path finder management functions
* ===================================================================== */
void initPathFinder() {
queue = new PriorityQueue<QueueItem, 192>;
squeue = new PriorityQueue<QueueItem, 128>;
objectVolumeArray = new TileRegion[128];
pathTileArray = (PathTilePosArray *)malloc( sizeof *pathTileArray);
maskComp = new MaskComputer;
cellArray = new PathArray;
PathRequest::tileArray = new PathTileRegion;
}
void cleanupPathFinder() {
if (pathTileArray) {
free(pathTileArray);
pathTileArray = nullptr;
}
if (maskComp) {
delete maskComp;
maskComp = nullptr;
}
if (cellArray != nullptr) {
delete cellArray;
cellArray = nullptr;
}
delete queue;
delete squeue;
delete[] objectVolumeArray;
delete PathRequest::tileArray;
}
} // end of namespace Saga2