#include "ht.h" namespace wi { /* for (j = 0; j < 64; j += 8) { var str = ""; for (i = 0; i < 8; i++) str += (j + i) * (j + i) + ", "; WScript.Echo(str); } */ int ganSquared[64] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, }; byte gmpdirdirOpposite[8] = { 4, 5, 6, 7, 0, 1, 2, 3 }; // Road // Open // Wall // Blocked #define knScoreBlocked 10000 #define knScoreStructure 9000 word TerrainMap::s_anScoreTerrain[] = { 0, 0, knScoreBlocked, knScoreBlocked }; // Cost of moving by one tile in a certain direction word TerrainMap::s_anScoreVector[] = { 5, 7, 5, 7, 5, 7, 5, 7 }; // Max nodes futility cutoff. Traverse the map three times, and * 3 because of how // nodes are added to the list #define kcNodesMax (ktcMax * 3 * 3) TerrainMap::TerrainMap() { m_ptrmaph = NULL; m_abBuffer = NULL; m_ppathhList = NULL; m_ppathhFreeList = NULL; m_cNodes = 0; } TerrainMap::~TerrainMap() { if (m_ptrmaph != NULL) gpakr.UnmapFile(&m_fmap); delete[] m_abBuffer; while (m_ppathhList != NULL) RemovePathHead(m_ppathhList); for (PathHead *ppathh = m_ppathhFreeList; ppathh != NULL; ) { PathHead *ppathhT = ppathh; ppathh = ppathh->ppathhNext; delete ppathhT; } } bool TerrainMap::Init(char *pszFn) { // Load the terrain map m_ptrmaph = (TerrainMapHeader *)gpakr.MapFile(pszFn, &m_fmap); if (m_ptrmaph == NULL) return false; // Alloc the path buffer m_ctx = BigWord(m_ptrmaph->ctx); m_cty = BigWord(m_ptrmaph->cty); m_cbBuffer = m_ctx * m_cty; m_abBuffer = new byte[m_cbBuffer]; Assert(m_abBuffer != NULL, "out of memory!"); if (m_abBuffer == NULL) return false; memset(m_abBuffer, 0, m_cbBuffer); // Init for (int n = 0; n < 8; n++) m_mpDirDelta[n] = g_mpDirToDy[n] * m_ctx + g_mpDirToDx[n]; return true; } #ifdef MP_DEBUG dword TerrainMap::GetChecksum() { dword n = 0; for (int i = 0; i < m_cbBuffer; i++) n += m_abBuffer[i]; return n; } #endif #define knVerTerrainMapState 1 bool TerrainMap::LoadState(Stream *pstm) { byte nVer = pstm->ReadByte(); if (nVer != knVerTerrainMapState) return false; pstm->ReadBytesRLE(m_abBuffer, m_cbBuffer); return pstm->IsSuccess(); } bool TerrainMap::SaveState(Stream *pstm) { pstm->WriteByte(knVerTerrainMapState); for (int n = 0; n < m_cbBuffer; n++) m_abBuffer[n] &= kbfMobileUnit | kbfStructure | kbfReserved; pstm->WriteBytesRLE(m_abBuffer, m_cbBuffer); return pstm->IsSuccess(); } void TerrainMap::SetFlags(int tx, int ty, int ctx, int cty, byte bf) { Assert(tx >= 0 && ty >= 0); Assert(tx + ctx <= m_ctx && ty + cty <= m_cty); for (int txT = tx; txT < tx + ctx; txT++) { for (int tyT = ty; tyT < ty + cty; tyT++) { m_abBuffer[tyT * m_ctx + txT] |= bf; } } #ifdef MP_DEBUG // MpTrace("Set Terrain Flags:%d,%d,%d,%d set 0x%02x", tx, ty, ctx, cty, bf); #endif } bool TerrainMap::TestFlags(int tx, int ty, int ctx, int cty, byte bf) { // We consider all tiles off the edge of the map to be fully occupied if (tx < 0 || ty < 0 || tx + ctx > m_ctx || ty + cty > m_cty) return true; for (int txT = tx; txT < tx + ctx; txT++) { for (int tyT = ty; tyT < ty + cty; tyT++) { int off = tyT * m_ctx + txT; if (m_abBuffer[off] & bf) return true; } } return false; } bool TerrainMap::IsOccupied(int tx, int ty, int ctx, int cty, byte bf) { // We consider all tiles off the edge of the map to be fully occupied if (tx < 0 || ty < 0 || tx + ctx > m_ctx || ty + cty > m_cty) return true; for (int txT = tx; txT < tx + ctx; txT++) { for (int tyT = ty; tyT < ty + cty; tyT++) { int off = tyT * m_ctx + txT; if (m_abBuffer[off] & bf) return true; if (((byte *)(m_ptrmaph + 1))[off] > kttOpen) return true; } } return false; } void TerrainMap::ClearFlags(int tx, int ty, int ctx, int cty, byte bf) { Assert(tx >= 0 && ty >= 0); Assert(tx + ctx <= m_ctx && ty + cty <= m_cty); for (int txT = tx; txT < tx + ctx; txT++) { for (int tyT = ty; tyT < ty + cty; tyT++) { m_abBuffer[tyT * m_ctx + txT] &= ~bf; } } #ifdef MP_DEBUG // MpTrace("Clear Terrain Flags:%d,%d,%d,%d clear 0x%02x", tx, ty, ctx, cty, bf); #endif } bool TerrainMap::IsLineOccupied(TCoord txFrom, TCoord tyFrom, TCoord txTo, TCoord tyTo, byte bfTerrainAvoid) { // There already? if (txFrom == txTo && tyFrom == tyTo) return false; // Number of x's and y's to travel int dx = abs(txTo - txFrom); int dy = abs(tyTo - tyFrom); // Direction on each axis int sx, sy; if (txFrom < txTo) { sx = 1; } else { sx = -1; } if (tyFrom < tyTo) { sy = 1; } else { sy = -1; } TCoord txT = txFrom; TCoord tyT = tyFrom; int nStep = 0; int dc = 0; while (true) { if (dx > dy) { txT += sx; dc += dy; if (dc >= dx) { dc -= dx; tyT += sy; } } else { tyT += sy; dc += dx; if (dc >= dy) { dc -= dy; txT += sx; } } // If this is blocking, bail if (IsBlocked(txT, tyT, bfTerrainAvoid)) return true; // If we're there, break if (txT == txTo && tyT == tyTo) break; } return false; } bool TerrainMap::FindFirstUnoccupied(TCoord txFrom, TCoord tyFrom, TCoord txTo, TCoord tyTo, TCoord *ptxFree, TCoord *ptyFree) { // Number of x's and y's to travel int dx = abs(txTo - txFrom); int dy = abs(tyTo - tyFrom); // Direction on each axis int sx, sy; if (txFrom < txTo) { sx = 1; } else { sx = -1; } if (tyFrom < tyTo) { sy = 1; } else { sy = -1; } // If this is blocking, bail TCoord txT = txFrom; TCoord tyT = tyFrom; if (!IsBlocked(txT, tyT, 0)) { *ptxFree = txT; *ptyFree = tyT; return true; } if (txFrom == txTo && tyFrom == tyTo) return false; int nStep = 0; int dc = 0; while (true) { if (dx > dy) { txT += sx; dc += dy; if (dc >= dx) { dc -= dx; tyT += sy; } } else { tyT += sy; dc += dx; if (dc >= dy) { dc -= dy; txT += sx; } } // If this is blocking, bail if (!IsBlocked(txT, tyT, 0)) { *ptxFree = txT; *ptyFree = tyT; return true; } // If we're there, break if (txT == txTo && tyT == tyTo) break; } return false; } bool TerrainMap::FindLastUnoccupied(TCoord txFrom, TCoord tyFrom, TCoord txTo, TCoord tyTo, TCoord *ptxFree, TCoord *ptyFree) { // Number of x's and y's to travel int dx = abs(txTo - txFrom); int dy = abs(tyTo - tyFrom); // Direction on each axis int sx, sy; if (txFrom < txTo) { sx = 1; } else { sx = -1; } if (tyFrom < tyTo) { sy = 1; } else { sy = -1; } // If this is not blocking, remember TCoord txT = txFrom; TCoord tyT = tyFrom; if (IsBlocked(txT, tyT, 0)) return false; *ptxFree = txT; *ptyFree = tyT; if (txFrom == txTo && tyFrom == tyTo) return true; int nStep = 0; int dc = 0; while (true) { if (dx > dy) { txT += sx; dc += dy; if (dc >= dx) { dc -= dx; tyT += sy; } } else { tyT += sy; dc += dx; if (dc >= dy) { dc -= dy; txT += sx; } } // If this is not blocking, remember it if (IsBlocked(txT, tyT, 0)) return true; *ptxFree = txT; *ptyFree = tyT; // If we're there, break if (txT == txTo && tyT == tyTo) break; } return true; } Path *TerrainMap::FindLinePath(TCoord txFrom, TCoord tyFrom, TCoord txTo, TCoord tyTo, byte bfTerrainAvoid) { if (txFrom == txTo && tyFrom == tyTo) return NULL; TPoint atpt[ktcMax + ktcMax / 2]; // Number of x's and y's to travel int dx = abs(txTo - txFrom); int dy = abs(tyTo - tyFrom); // Direction on each axis int sx, sy; if (txFrom < txTo) { sx = 1; } else { sx = -1; } if (tyFrom < tyTo) { sy = 1; } else { sy = -1; } TCoord txT = txFrom; TCoord tyT = tyFrom; int nStep = 0; int dc = 0; while (true) { if (dx > dy) { txT += sx; dc += dy; if (dc >= dx) { dc -= dx; tyT += sy; } } else { tyT += sy; dc += dx; if (dc >= dy) { dc -= dy; txT += sx; } } // If this is blocking, bail if (IsBlocked(txT, tyT, bfTerrainAvoid)) return NULL; // Location looks good, save it atpt[nStep].tx = (byte)txT; atpt[nStep].ty = (byte)tyT; nStep++; Assert(nStep < ARRAYSIZE(atpt)); // If we're there, break if (txT == txTo && tyT == tyTo) break; } // Create a path from this return CreatePath(this, txFrom, tyFrom, atpt, nStep); } Path *TerrainMap::FindPath(int txFrom, int tyFrom, int txTo, int tyTo, byte bfTerrainAvoid) { // Arbitrary pathing if (txFrom == txTo && tyFrom == tyTo) return NULL; // Quick out. Miners do this when they want to get to the dump point but it's set because // the processor is occupied. if (abs(txFrom - txTo) <= 1 && abs(tyFrom - tyTo) <= 1) { TPoint tptT; tptT.tx = txTo; tptT.ty = tyTo; return CreatePath(this, txFrom, tyFrom, &tptT, 1); } // Initialize. #if 0 for (int n = 0; n < m_cbBuffer; n++) m_abBuffer[n] &= kbfMobileUnit | kbfStructure | kbfReserved; #else #define kbClear (kbfMobileUnit | kbfStructure | kbfReserved) #define kdwClear MakeDword(kbClear, kbClear, kbClear, kbClear) // Try to speed up initial buffer clearning // If not dword aligned, adjust byte *pbT = m_abBuffer; if (*((byte *)&pbT) & 2) { *pbT++ &= kbClear; *pbT++ &= kbClear; } // 32 byte aligned count of dwords dword *pdwT = (dword *)pbT; int cdw = (((dword *)&m_abBuffer[m_cbBuffer]) - pdwT) & ~7; dword *pdwMax = &((dword *)m_abBuffer)[cdw]; dword dwClear = kdwClear; while (pdwT < pdwMax) { *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; *pdwT++ &= dwClear; } // Clear dwords (up to 7 dwords) pdwT = pdwMax; int cdwT = (int)(((dword *)&m_abBuffer[m_cbBuffer]) - pdwT); pdwMax = &pdwT[cdwT]; while (pdwT < pdwMax) *pdwT++ &= dwClear; // Clear remaining bytes (up to 3 bytes) switch (&m_abBuffer[m_cbBuffer] - (byte *)pdwMax) { case 0: break; case 1: ((byte *)pdwMax)[0] &= kbClear; break; case 2: ((byte *)pdwMax)[0] &= kbClear; ((byte *)pdwMax)[1] &= kbClear; break; case 3: ((byte *)pdwMax)[0] &= kbClear; ((byte *)pdwMax)[1] &= kbClear; ((byte *)pdwMax)[2] &= kbClear; break; default: Assert(); break; } #endif while (m_ppathhList != NULL) RemovePathHead(m_ppathhList); Assert(m_ppathhList == NULL); byte *pbTo = &m_abBuffer[tyTo * m_ctx + txTo]; PathHead pathh; memset(&pathh, 0, sizeof(pathh)); pathh.offHead = tyFrom * m_ctx + txFrom; pathh.txLast = txFrom; pathh.tyLast = tyFrom; PathHead *ppathhLast = &pathh; m_abBuffer[pathh.offHead] |= kbfLinked; word offClosest; word nDistClosest = (word)-1; while (ppathhLast != NULL) { for (int n = 0; n < 8; n++) { // Get next pos int txNew = ppathhLast->txLast + g_mpDirToDx[n]; int tyNew = ppathhLast->tyLast + g_mpDirToDy[n]; // Check out of bounds. If we use 64*64 maps, we can use an AND mask // of 0x2040 to determine out of bounds conditions. if (txNew < 0 || txNew >= m_ctx || tyNew < 0 || tyNew >= m_cty) continue; // If this tile is linked already, abandon this path since // it is longer byte *pbNew = &m_abBuffer[ppathhLast->offHead] + m_mpDirDelta[n]; if (*pbNew & kbfLinked) continue; // The tile it came from is no longer a head if (m_abBuffer[ppathhLast->offHead] & kbfHead) { m_abBuffer[ppathhLast->offHead] &= ~kbfHead; if (ppathhLast != &pathh) { pathh = *ppathhLast; RemovePathHead(ppathhLast); ppathhLast = &pathh; } } // Calc score of terrain word offHead = pbNew - m_abBuffer; word wScoreTerrain; if (*pbNew & bfTerrainAvoid) { wScoreTerrain = knScoreStructure; } else { wScoreTerrain = s_anScoreTerrain[((byte *)(m_ptrmaph + 1))[offHead]]; } #ifdef STATS_DISPLAY gcPathScoresCalced++; #endif // Link in this square, add it to the list. It is a path head. *pbNew = (*pbNew & (kbfMobileUnit | kbfStructure | kbfReserved)) | kbfHead | kbfLinked | n; word wScoreKnown = ppathhLast->wScoreKnown + s_anScoreVector[n] + wScoreTerrain; word nDist = ganSquared[abs(txNew - txTo)] + ganSquared[abs(tyNew - tyTo)]; word wScore = wScoreKnown + nDist; PathHead *ppathhNew = AddPathHead(wScoreKnown, wScore, ppathhLast->cSteps + 1, offHead, txNew, tyNew); // Remember "closest" point if (nDist < nDistClosest) { nDistClosest = nDist; offClosest = pbNew - m_abBuffer; } // Either out of memory or max # nodes reached. Return best path so far if (ppathhNew == NULL) return MakePath(txFrom, tyFrom, offClosest); // Are we at the destination? Then we're done! if (pbNew == pbTo) return MakePath(txFrom, tyFrom, ppathhNew->offHead); } // Perhaps this was a dead end? if (ppathhLast == m_ppathhList) RemovePathHead(ppathhLast); // Grab the best path and do again ppathhLast = m_ppathhList; } return NULL; } PathHead *TerrainMap::AddPathHead(word wScoreKnown, word wScore, int cSteps, word offHead, int txNew, int tyNew) { // Alloc PathHead *ppathh; if (m_ppathhFreeList != NULL) { ppathh = m_ppathhFreeList; m_ppathhFreeList = ppathh->ppathhNext; } else { if (m_cNodes >= kcNodesMax) return NULL; ppathh = new PathHead; if (ppathh == NULL) return NULL; m_cNodes++; } // Initialize ppathh->txLast = txNew; ppathh->tyLast = tyNew; ppathh->cSteps = cSteps; ppathh->offHead = offHead; ppathh->wScoreKnown = wScoreKnown; ppathh->wScore = wScore; // Sorted insertion // OPT: The fast approach here will be to use a binary tree of some variety // (AVL / Right Handed / Splay etc, TBD). For the time being, using // a simple linear list w/scanning for simplicity to get searching // working. for (PathHead **pppathhT = &m_ppathhList; true; pppathhT = &(*pppathhT)->ppathhNext) { if (*pppathhT == NULL) { *pppathhT = ppathh; ppathh->ppathhNext = NULL; break; } if (ppathh->wScore <= (*pppathhT)->wScore) { ppathh->ppathhNext = *pppathhT; *pppathhT = ppathh; break; } } return ppathh; } void TerrainMap::RemovePathHead(PathHead *ppathh) { for (PathHead **pppathhT = &m_ppathhList; (*pppathhT) != NULL; pppathhT = &(*pppathhT)->ppathhNext) { if (*pppathhT == ppathh) { *pppathhT = ppathh->ppathhNext; ppathh->ppathhNext = m_ppathhFreeList; m_ppathhFreeList = ppathh; return; } } Assert(); } bool TerrainMap::IsBlocked(TCoord tx, TCoord ty, byte bf) { word offMap = ty * m_ctx + tx; // We path through structures and blocked terrain (at very high cost) so that structures or // terrain that are "blocking" can at least be approached (albeit at high pathing cost) // as opposed to be totally blocked and never considered. Then at path following we determine // if there is blockage with this method. if (m_abBuffer[offMap] & bf) return true; if (((byte *)(m_ptrmaph + 1))[offMap] > kttOpen) return true; return false; } int TerrainMap::GetTerrainType(TCoord tx, TCoord ty) { Assert(tx >= 0 && tx < m_ctx && ty >= 0 && ty < m_cty); word offMap = ty * m_ctx + tx; return ((byte *)(m_ptrmaph + 1))[offMap]; } bool TerrainMap::GetFlags(int tx, int ty, byte *pbf) { Assert(tx >= 0 && ty >= 0); Assert(tx < m_ctx && ty < m_cty); word offMap = ty * m_ctx + tx; if (((byte *)(m_ptrmaph + 1))[offMap] > kttOpen) return false; *pbf = m_abBuffer[offMap]; return true; } Path *TerrainMap::MakePath(TCoord txStart, TCoord tyStart, word off) { Direction adir[kcNodesMax]; TCoord ty = off / m_ctx; TCoord tx = off % m_ctx; byte *pbPathWalk = &m_abBuffer[off]; Direction *pdirT = &adir[ARRAYSIZE(adir)]; while (tx != txStart || ty != tyStart) { Assert(*pbPathWalk & kbfLinked); Direction dir = (*pbPathWalk) & kbfDirMask; pdirT--; *pdirT = dir; Direction dirOpposite = gmpdirdirOpposite[dir]; pbPathWalk += m_mpDirDelta[dirOpposite]; tx += g_mpDirToDx[dirOpposite]; ty += g_mpDirToDy[dirOpposite]; } return CreatePath(this, txStart, tyStart, pdirT, (int)(&adir[ARRAYSIZE(adir)] - pdirT)); } // // Path class // Path *CreatePath(TerrainMap *ptrmap, TCoord txStart, TCoord tyStart, TPoint *atpt, int ctpt) { Direction adir[kcNodesMax]; TCoord txLast = txStart; TCoord tyLast = tyStart; for (int idir = 0; idir < ctpt; idir++) { adir[idir] = DirectionFromLocations(txLast, tyLast, atpt[idir].tx, atpt[idir].ty); txLast = atpt[idir].tx; tyLast = atpt[idir].ty; } return CreatePath(ptrmap, txStart, tyStart, adir, ctpt); } Path *CreatePath(TerrainMap *ptrmap, TCoord txStart, TCoord tyStart, Direction *adir, int cdir) { Path *ppath = new Path; if (ppath == NULL) return NULL; if (!ppath->Init(ptrmap, txStart, tyStart, adir, cdir)) { delete ppath; return NULL; } return ppath; } Path::Path() { m_cdirs = 0; m_adir = NULL; } Path::~Path() { delete[] m_adir; } bool Path::Init(TerrainMap *ptrmap, TCoord txStart, TCoord tyStart, Direction *adir, int cdir) { m_adir = new Direction[cdir]; if (m_adir == NULL) return false; memcpy(m_adir, adir, cdir * sizeof(Direction)); m_cdirs = cdir; m_txStart = txStart; m_tyStart = tyStart; // Init cache m_idirCache = -1; m_txCache = txStart; m_tyCache = tyStart; m_ptrmap = ptrmap; return true; } int Path::GetCount() { return m_cdirs; } bool Path::GetPoint(int itpt, TPoint *ptpt, byte bf) { if (itpt < 0 || itpt >= m_cdirs) return false; TCoord txT, tyT; CalcTo(itpt, &txT, &tyT); if (m_ptrmap->IsBlocked(txT, tyT, bf)) return false; ptpt->tx = txT; ptpt->ty = tyT; return true; } bool Path::GetPointRaw(int itpt, TPoint *ptpt) { if (itpt < 0 || itpt >= m_cdirs) return false; TCoord txT, tyT; CalcTo(itpt, &txT, &tyT); ptpt->tx = txT; ptpt->ty = tyT; return true; } void Path::SetCacheIndex(int idir) { if (idir < 0 || idir >= m_cdirs) return; TCoord txT, tyT; CalcTo(idir, &txT, &tyT); m_idirCache = idir; m_txCache = txT; m_tyCache = tyT; } void Path::CalcTo(int idir, TCoord *ptx, TCoord *pty) { if (idir < m_idirCache) { m_idirCache = -1; m_txCache = m_txStart; m_tyCache = m_tyStart; Assert(false); } int idirT = m_idirCache; TCoord txT = m_txCache; TCoord tyT = m_tyCache; while (true) { idirT++; if (idirT > idir) break; txT += g_mpDirToDx[m_adir[idirT]]; tyT += g_mpDirToDy[m_adir[idirT]]; } *ptx = txT; *pty = tyT; } int Path::FindClosestPoint(TCoord txFrom, TCoord tyFrom, int itptStart, int ctptFurtherStop, TPoint *ptptClosest) { int ctptRemaining = m_cdirs - itptStart; word n2Last = (word)-1; word nSmallest = (word)-1; int jSmallest = -1; int ctptFurtherStopT = ctptFurtherStop; for (int j = 0; j < ctptRemaining; j++) { // Get location. False would mean it's blocked TPoint tpt; if (!GetPointRaw(j + itptStart, &tpt)) break; // If we're on the path, return that if (tpt.tx == txFrom && tpt.ty == tyFrom) { *ptptClosest = tpt; return j + itptStart; } // Calc smallest distance, always advance if we get smaller word n2 = ganSquared[abs(tpt.tx - txFrom)] + ganSquared[abs(tpt.ty - tyFrom)]; if (n2 <= nSmallest) { nSmallest = n2; jSmallest = j; *ptptClosest = tpt; } // If we've increased distance the last cFurtherAwayStop times, stop if (n2 > n2Last) { ctptFurtherStopT--; if (ctptFurtherStopT == 0) break; } else { ctptFurtherStopT = ctptFurtherStop; } n2Last = n2; } if (jSmallest == -1) return -1; return jSmallest + itptStart; } bool Path::Append(Path *ppath) { Direction *adirNew = new Direction[m_cdirs + ppath->m_cdirs]; if (adirNew == NULL) return false; memcpy(adirNew, m_adir, m_cdirs); memcpy(&adirNew[m_cdirs], ppath->m_adir, ppath->m_cdirs); delete[] m_adir; m_adir = adirNew; m_cdirs += ppath->m_cdirs; return true; } bool Path::TrimEnd(int itptStart) { if (itptStart >= m_cdirs) return false; int cdirsNew = itptStart; Direction *adirNew = new Direction[cdirsNew]; if (adirNew == NULL) return false; memcpy(adirNew, m_adir, cdirsNew); delete[] m_adir; m_adir = adirNew; m_cdirs = cdirsNew; return true; } Path *Path::Clone() { Path *ppathNew = new Path(); if (ppathNew == NULL) return NULL; ppathNew->m_ptrmap = m_ptrmap; ppathNew->m_cdirs = m_cdirs; ppathNew->m_txStart = m_txStart; ppathNew->m_tyStart = m_tyStart; ppathNew->m_idirCache = -1; ppathNew->m_txCache = m_txStart; ppathNew->m_tyCache = m_tyStart; ppathNew->m_adir = new Direction[m_cdirs]; if (ppathNew->m_adir == NULL) { delete ppathNew; return NULL; } memcpy(ppathNew->m_adir, m_adir, m_cdirs); return ppathNew; } void Path::GetStartPoint(TPoint *ptpt) { ptpt->tx = m_txStart; ptpt->ty = m_tyStart; } #define knVerPathState 4 bool Path::LoadState(TerrainMap *ptrmap, Stream *pstm) { byte nVer = pstm->ReadByte(); if (nVer != knVerPathState) return false; m_ptrmap = ptrmap; m_cdirs = pstm->ReadWord(); m_txStart = pstm->ReadWord(); m_tyStart = pstm->ReadWord(); m_adir = new Direction[m_cdirs]; if (m_adir == NULL) return false; m_idirCache = (int)(short)pstm->ReadWord(); m_txCache = pstm->ReadWord(); m_tyCache = pstm->ReadWord(); byte bT; for (int idir = 0; idir < m_cdirs; idir++) { Direction dir; if ((idir & 1) == 0) { bT = pstm->ReadByte(); dir = (bT & 0xf0) >> 4; } else { dir = bT & 0x0f; } m_adir[idir] = dir; } // Done return pstm->IsSuccess(); } bool Path::SaveState(Stream *pstm) { pstm->WriteByte(knVerPathState); pstm->WriteWord(m_cdirs); pstm->WriteWord(m_txStart); pstm->WriteWord(m_tyStart); pstm->WriteWord(m_idirCache); pstm->WriteWord(m_txCache); pstm->WriteWord(m_tyCache); // 2 directions per byte, smaller encoding byte bT = 0; for (int idir = 0; idir < m_cdirs; idir++) { Direction dir = m_adir[idir]; if ((idir & 1) == 0) { bT = dir << 4; if (idir == m_cdirs - 1) pstm->WriteByte(bT); } else { bT |= dir; pstm->WriteByte(bT); } } // Done return pstm->IsSuccess(); } #ifdef DRAW_PATHS void Path::Draw(DibBitmap *pbm, int xView, int yView, Side side) { TCoord txOld = m_txStart; TCoord tyOld = m_tyStart; for (int idir = 0; idir < m_cdirs; idir++) { Direction dir = m_adir[idir]; TCoord txNew = txOld + g_mpDirToDx[dir]; TCoord tyNew = tyOld + g_mpDirToDy[dir]; DrawArrow(pbm, PcFromTc(txOld) - xView, PcFromTc(tyOld) - yView, dir, side); txOld = txNew; tyOld = tyNew; } // Draw 'X' at destination DrawArrow(pbm, PcFromTc(txOld) - xView, PcFromTc(tyOld) - yView, 8, side); } #endif Direction DirectionFromLocations(TCoord txOld, TCoord tyOld, TCoord txNew, TCoord tyNew) { Assert(abs(txOld - txNew) <= 1 && abs(tyOld - tyNew) <= 1); Direction dir; if (txNew < txOld) { if (tyNew < tyOld) { dir = kdirNW; } else if (tyNew > tyOld) { dir = kdirSW; } else { dir = kdirW; } } else if (txNew > txOld) { if (tyNew < tyOld) { dir = kdirNE; } else if (tyNew > tyOld) { dir = kdirSE; } else { dir = kdirE; } } else { if (tyNew < tyOld) { dir = kdirN; } else if (tyNew > tyOld) { dir = kdirS; } else { Assert(false); dir = kdirInvalid; } } return dir; } // // TrackPoint class // bool TrackPoint::Init(Path *ppath, TCoord txFrom, TCoord tyFrom, int itptStart, int ctptFurtherStop) { // Create a tracking point from txFrom, tyFrom to the path m_itptClosest = ppath->FindClosestPoint(txFrom, tyFrom, itptStart, ctptFurtherStop, &m_tptClosest); if (m_itptClosest == -1) return false; // Get the previous and next locations if (!ppath->GetPoint(m_itptClosest - 1, &m_tptBefore, 0)) m_tptBefore = m_tptClosest; if (!ppath->GetPoint(m_itptClosest + 1, &m_tptAfter, 0)) m_tptAfter = m_tptClosest; // Measure current distances to these locations. We'll measure // progress against these m_n2Before = ganSquared[abs(txFrom - m_tptBefore.tx)] + ganSquared[abs(tyFrom - m_tptBefore.ty)]; m_n2After = ganSquared[abs(txFrom - m_tptAfter.tx)] + ganSquared[abs(tyFrom - m_tptAfter.ty)]; m_tptInitial.tx = txFrom; m_tptInitial.ty = tyFrom; return true; } void TrackPoint::InitFrom(TrackPoint *ptrkp) { m_tptInitial = ptrkp->m_tptInitial; m_tptClosest = ptrkp->m_tptClosest; m_tptBefore = ptrkp->m_tptBefore; m_tptAfter = ptrkp->m_tptAfter; m_itptClosest = ptrkp->m_itptClosest; m_n2Before = ptrkp->m_n2Before; m_n2After = ptrkp->m_n2After; } bool TrackPoint::IsProgress(TrackPoint *ptrkpA) { // Returns true if this is closer than ptrkp // First compare closest path point. Longer is better "progress" TrackPoint *ptrkpB = this; if (ptrkpA->m_itptClosest < ptrkpB->m_itptClosest) return false; if (ptrkpA->m_itptClosest > ptrkpB->m_itptClosest) return true; // Either same distance to before and closer to after, or further from before and same or greater to // after or closer to both before and after are recognized as "progress". if ((ptrkpA->m_n2Before == ptrkpB->m_n2Before && ptrkpA->m_n2After < ptrkpB->m_n2After) || (ptrkpA->m_n2Before > ptrkpB->m_n2Before && ptrkpA->m_n2After <= ptrkpB->m_n2After) || (ptrkpA->m_n2Before < ptrkpB->m_n2Before && ptrkpA->m_n2After < ptrkpB->m_n2After)) { return true; } return false; } bool TrackPoint::IsCloser(TrackPoint *ptrkpA) { // Is ptrkpA closer to the unit path than ptrkpB? If so, it is closer. If same distance, // measure progress. TrackPoint *ptrkpB = this; if (ptrkpA->m_n2After < ptrkpB->m_n2After) return true; if (ptrkpA->m_n2After == ptrkpB->m_n2After) return IsProgress(ptrkpA); return false; } bool TrackPoint::IsBetterSort(TrackPoint *ptrkpA) { return IsProgress(ptrkpA); #if 0 TrackPoint *ptrkpB = this; if (ptrkpA->m_n2After < ptrkpB->m_n2After) return true; if (ptrkpA->m_n2After == ptrkpB->m_n2After) { if (ptrkpA->m_n2Before > ptrkpB->m_n2Before) return true; } return false; #endif } } // namespace wi