void THPathfinder::pathingInit(const THMap *pMap, int iStartX, int iStartY, int *piWidth, node_t *ppNode, int iGuessNumber) { *piWidth = pMap->getWidth(); m_pDestination = NULL; _allocNodeCache(*piWidth, pMap->getHeight()); *ppNode = m_pNodes + iStartY * *piWidth + iStartX; pNode->prev = NULL; pNode->distance = 0; if (iGuessNumber == 1) MakeGuess1(*ppNode); if (iGuessNumber == 2) MakeGuess2(*ppNode); if (iGuessNumber == 3) MakeGuess3(*ppNode); if (iGuessNumber == 4) MakeGuess4(*ppNode); m_ppDirtyList[0] = *ppNode; m_iDirtyCount = 1; m_iOpenCount = 0; } /** No need to check for the node being on the map edge, as the N/E/S/W flags are set as to prevent travelling off the map (as well as to prevent walking through walls). */ THPathfinder::pathingNeighbours(uint32_t iFlags, int iWidth, int iTryNumber) { uint32_t iPassable = iFlags & THMN_Passable; if(iFlags & THMN_CanTravelW) { if (iTryNumber == 1) TryNode1(pNode - 1, 3, iTryNumber); if (iTryNumber == 2) TryNode2(pNode - 1, 3, iTryNumber); if (iTryNumber == 3) TryNode3(pNode - 1, 3, iTryNumber); if (iTryNumber == 4) TryNode4(pNode - 1, 3, iTryNumber); } if(iFlags & THMN_CanTravelE) { if (iTryNumber == 1) TryNode1(pNode + 1, 1, iTryNumber); if (iTryNumber == 2) TryNode2(pNode + 1, 1, iTryNumber); if (iTryNumber == 3) TryNode3(pNode + 1, 1, iTryNumber); if (iTryNumber == 4) TryNode4(pNode + 1, 1, iTryNumber); } if(iFlags & THMN_CanTravelN) { if (iTryNumber == 1) TryNode1(pNode - iWidth, 0, iTryNumber); if (iTryNumber == 2) TryNode2(pNode - iWidth, 0, iTryNumber); if (iTryNumber == 3) TryNode3(pNode - iWidth, 0, iTryNumber); if (iTryNumber == 4) TryNode4(pNode - iWidth, 0, iTryNumber); } if(iFlags & THMN_CanTravelS) { if (iTryNumber == 1) TryNode1(pNode + iWidth, 2, iTryNumber); if (iTryNumber == 2) TryNode2(pNode + iWidth, 2, iTryNumber); if (iTryNumber == 3) TryNode3(pNode + iWidth, 2, iTryNumber); if (iTryNumber == 4) TryNode4(pNode + iWidth, 2, iTryNumber); } } void THPathfinder::pathingTryNode(uint32_t iNFlags, uint32_t iPassable, node_t *pNeighbour, node_t *pNode, int iGuessNumber) { if(iNFlags & THMN_Passable || iPassable == 0) { if(pNeighbour->prev == pNeighbour) { pNeighbour->prev = pNode; pNeighbour->distance = pNode->distance + 1; if (iGuessNumber == 1) MakeGuess1(pNeighbour); if (iGuessNumber == 2) MakeGuess2(pNeighbour); if (iGuessNumber == 3) MakeGuess3(pNeighbour); if (iGuessNumber == 4) MakeGuess4(pNeighbour); m_ppDirtyList[m_iDirtyCount++] = pNeighbour; _openHeapPush(pNeighbour); } else if((pNode->distance + 1) < pNeighbour->distance) { pNeighbour->prev = pNode; pNeighbour->distance = pNode->distance + 1; /* Guess doesn't change, and already in the dirty list. */ _openHeapPromote(pNeighbour); } } } bool THPathfinder::findPath(const THMap *pMap, int iStartX, int iStartY, int iEndX, int iEndY) { if(pMap == NULL) pMap = m_pDefaultMap; if(pMap == NULL || pMap->getNode(iEndX, iEndY) == NULL || (pMap->getNodeUnchecked(iEndX, iEndY)->iFlags & THMN_Passable) == 0) { m_pDestination = NULL; return false; } // As diagonal movement is not allowed, the minimum distance between two // points is the sum of the distance in X and the distance in Y. Provided // that the compiler generates clever assembly for abs(), this means that // guesses can be calculated without branching and without sqrt(). #define MakeGuess1(pNode) \ pNode->guess = abs(pNode->x - iEndX) + abs(pNode->y - iEndY) pathingInit(pMap, iStartX, iStartY, &iWidth, &pNode, 1); node_t *pTarget = m_pNodes + iEndY * iWidth + iEndX; while(true) { if(pNode == pTarget) { m_pDestination = pTarget; return true; } uint32_t iFlags = pMap->getNodeUnchecked(pNode->x, pNode->y)->iFlags; #define TryNode1(n, d, iGuessNumber) \ node_t *pNeighbour = n; \ uint32_t iNFlags = pMap->getNodeUnchecked(pNeighbour->x, pNeighbour->y)->iFlags; \ pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber) pathingNeighbours(iFlags, iWidth, 1); if(m_iOpenCount == 0) { m_pDestination = NULL; break; } else pNode = _openHeapPop(); } return false; #undef MakeGuess1 #undef TryNode } bool THPathfinder::findPathToHospital(const THMap *pMap, int iStartX, int iStartY) { if(pMap == NULL) pMap = m_pDefaultMap; if(pMap == NULL || pMap->getNode(iStartX, iStartY) == NULL || (pMap->getNodeUnchecked(iStartX, iStartY)->iFlags & THMN_Passable) == 0) { m_pDestination = NULL; return false; } #define MakeGuess2(pNode) pNode->guess = 0 pathingInit(pMap, iStartX, iStartY, &iWidth, &pNode, 2); while(true) { uint32_t iFlags = pMap->getNodeUnchecked(pNode->x, pNode->y)->iFlags; if(iFlags & THMN_Hospital) { m_pDestination = pNode; return true; } #define TryNode2(n, d, iGuessNumber) \ node_t *pNeighbour = n; \ uint32_t iNFlags = pMap->getNodeUnchecked(pNeighbour->x, pNeighbour->y)->iFlags; \ pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber) pathingNeighbours(iFlags, iWidth, 2); if(m_iOpenCount == 0) { m_pDestination = NULL; break; } else pNode = _openHeapPop(); } return false; #undef MakeGuess #undef TryNode } bool THPathfinder::findIdleTile(const THMap *pMap, int iStartX, int iStartY, int iN) { if(pMap == NULL) pMap = m_pDefaultMap; if(pMap == NULL) { m_pDestination = NULL; return false; } #define MakeGuess3(pNode) \ pNode->guess = 0 pathingInit(pMap, iStartX, iStartY, &iWidth, &pNode, 3); node_t *pPossibleResult = NULL; while(true) { pNode->open_idx = -1; uint32_t iFlags = pMap->getNodeUnchecked(pNode->x, pNode->y)->iFlags; if((iFlags & THMN_DoNotIdle) == 0 && (iFlags & THMN_Passable) && (iFlags & THMN_Hospital)) { if(iN == 0) { m_pDestination = pNode; return true; } else { pPossibleResult = pNode; --iN; } } node_t* pBestNext = NULL; double fBestDistance = 0.0; #define TryNode3(n, d, iGuessNumber) \ node_t *pNeighbour = n; \ uint32_t iNFlags = pMap->getNodeUnchecked(pNeighbour->x, pNeighbour->y)->iFlags; \ /* When finding an idle tile, do not navigate through doors */ \ switch(d) \ { \ case 0: \ if((iFlags & THMN_DoorNorth) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 1: \ if((iNFlags & THMN_DoorWest) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 2: \ if((iNFlags & THMN_DoorNorth) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 3: \ if((iFlags & THMN_DoorWest) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ } \ /* Identify the neighbour in the open list nearest to the start */ \ if(pNeighbour->prev != pNeighbour && pNeighbour->open_idx != -1) \ { \ int iDX = pNeighbour->x - iStartX; \ int iDY = pNeighbour->y - iStartY; \ double fDistance = sqrt((double)(iDX * iDX + iDY * iDY)); \ if(pBestNext == NULL || fDistance < fBestDistance) \ pBestNext = pNeighbour, fBestDistance = fDistance; \ } pathingNeighbours(iFlags, iWidth, 3); if(m_iOpenCount == 0) { m_pDestination = NULL; break; } if(pBestNext) { // Promote the best neighbour to the front of the open list // This causes sequential iN to give neighbouring results for most iN pBestNext->guess = -pBestNext->distance; _openHeapPromote(pBestNext); } pNode = _openHeapPop(); } if(pPossibleResult) { m_pDestination = pPossibleResult; return true; } return false; #undef MakeGuess #undef TryNode } bool THPathfinder::visitObjects(const THMap *pMap, int iStartX, int iStartY, THObjectType eTHOB, int iMaxDistance, lua_State *L, int iVisitFunction, bool anyObjectType) { if(pMap == NULL) pMap = m_pDefaultMap; if(pMap == NULL) { m_pDestination = NULL; return false; } #define MakeGuess4(pNode) \ pNode->guess = 0 pathingInit(pMap, iStartX, iStarty, &iWidth, &pNode, 4); uint32_t iTHOB = static_cast(eTHOB) << 24; while(true) { uint32_t iFlags = pMap->getNodeUnchecked(pNode->x, pNode->y)->iFlags; #define TryNode4(n, d, iGuessNumber) \ node_t *pNeighbour = n; \ int iObjectNumber = 0; \ const THMapNode *pMapNode = pMap->getNodeUnchecked(pNeighbour->x, pNeighbour->y);\ uint32_t iNFlags = pMap->getNodeUnchecked(pNeighbour->x, pNeighbour->y)->iFlags; \ if ((iNFlags & 0xFF000000) == iTHOB) \ iObjectNumber = 1; \ if(pMapNode->pExtendedObjectList != NULL)\ {\ int count = *pMapNode->pExtendedObjectList & 7;\ for(int i = 0; i < count; i++) \ { \ int thob = (*pMapNode->pExtendedObjectList & (255 << (3 + (i << 3)))) >> (3 + (i << 3)); \ if(thob == eTHOB)\ iObjectNumber++; \ } \ } \ if(anyObjectType) \ iObjectNumber = 1; \ bool bSucces = false; \ for(int i = 0; i < iObjectNumber; i++) \ { \ /* call the given Lua function, passing four arguments: */ \ /* The x and y position of the object (Lua tile co-ords) */ \ /* The direction which was last travelled in to reach (x,y); */ \ /* 0 (north), 1 (east), 2 (south), 3 (west) */ \ /* The distance to the object from the search starting point */ \ lua_pushvalue(L, iVisitFunction); \ lua_pushinteger(L, pNeighbour->x + 1); \ lua_pushinteger(L, pNeighbour->y + 1); \ lua_pushinteger(L, d); \ lua_pushinteger(L, pNode->distance); \ lua_call(L, 4, 1); \ if(lua_toboolean(L, -1) != 0) \ { \ bSucces = true; \ } \ lua_pop(L, 1); \ } \ if(bSucces) \ return true; \ if(pNode->distance < iMaxDistance) \ { \ switch(d) \ { \ case 0: \ if((iFlags & THMN_DoorNorth) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 1: \ if((iNFlags & THMN_DoorWest) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 2: \ if((iNFlags & THMN_DoorNorth) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ case 3: \ if((iFlags & THMN_DoorWest) == 0) { pathingTryNode(iNFlags, iPassable, pNeighbour, pNode, iGuessNumber); } \ break; \ } \ } pathingNeighbours(iFlags, iWidth, 4); if(m_iOpenCount == 0) { m_pDestination = NULL; break; } else pNode = _openHeapPop(); } return false; #undef MakeGuess #undef TryNode }