// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" #include "game_share/utils.h" #include "world_map.h" //extern bool simulateBug(int bugId); using namespace std; using namespace NLMISC; namespace RYAI_MAP_CRUNCH { NL_BEGIN_STRING_CONVERSION_TABLE (TAStarFlag) NL_STRING_CONVERSION_TABLE_ENTRY(Nothing) NL_STRING_CONVERSION_TABLE_ENTRY(Interior) NL_STRING_CONVERSION_TABLE_ENTRY(Water) NL_STRING_CONVERSION_TABLE_ENTRY(NoGo) NL_STRING_CONVERSION_TABLE_ENTRY(WaterAndNogo) NL_STRING_CONVERSION_TABLE_ENTRY(GroundFlags) NL_END_STRING_CONVERSION_TABLE(TAStarFlag, AStarFlagConversion, Nothing) const std::string& toString(TAStarFlag flag) { return AStarFlagConversion.toString(flag); } TAStarFlag toAStarFlag(const std::string& str) { return AStarFlagConversion.fromString(str); } ////////////////////////////////////////////////////////////////////////////// // Helper classes and data // ////////////////////////////////////////////////////////////////////////////// const struct CDirection::CDirectionData CDirection::directionDatas[] = { { +1, 0, ORTHO_COST}, { +1, +1, DIAG_COST}, { 0, +1, ORTHO_COST}, { -1, +1, DIAG_COST}, { -1, 0, ORTHO_COST}, { -1, -1, DIAG_COST}, { 0, -1, ORTHO_COST}, { +1, -1, DIAG_COST}, { 0, 0, NO_COST} }; // Enum vals .. // 5 6 7 // 4 8 0 // 3 2 1 const CDirection::TDirection CDirection::table[] = { CDirection::SW, CDirection::S, CDirection::SE, CDirection::W, CDirection::UNDEFINED, CDirection::E, CDirection::NW, CDirection::N, CDirection::NE }; ////////////////////////////////////////////////////////////////////////////// // // ////////////////////////////////////////////////////////////////////////////// class CABaseStarNode { public: CABaseStarNode(uint father, float distance, bool open); void setOpen(bool open) { _Open = open; } bool isOpened() const { return _Open; } float getDistance() const { return _Distance; } uint getFather() const { return _Father; } private: uint _Father; ///< Parent node in the path from the start position float _Distance; bool _Open; ///< Is the node in the OPEN or CLOSED set? }; inline CABaseStarNode::CABaseStarNode(uint father, float distance, bool open) : _Father(father) , _Distance(distance) , _Open(open) { } ////////////////////////////////////////////////////////////////////////////// // CAStarHeapNode // ////////////////////////////////////////////////////////////////////////////// class CAStarHeapNode : public CABaseStarNode { public: explicit CAStarHeapNode(CTopology::TTopologyRef Ref, uint Father, float Distance, bool Open); CTopology::TTopologyRef const& getRef() const { return _Ref; } private: CTopology::TTopologyRef _Ref; }; inline CAStarHeapNode::CAStarHeapNode(CTopology::TTopologyRef Ref, uint Father, float Distance, bool Open) : CABaseStarNode(Father, Distance, Open) , _Ref(Ref) { } ////////////////////////////////////////////////////////////////////////////// // CAStarNode // ////////////////////////////////////////////////////////////////////////////// class CAStarNode { public: CAStarNode() { } explicit CAStarNode(CWorldPosition const& pos) : _x(pos.xCoord().getUnitId()) , _y(pos.yCoord().getUnitId()), _slot(pos.slot()) { } CAStarNode(const CAStarNode & other) : _x(other._x), _y(other._y), _slot(other._slot) { } void updateMapPosition(CMapPosition& _mapPos) const; bool operator==(CAStarNode const& other) const; bool operator!=(CAStarNode const& other) const; bool operator<(CAStarNode const& other) const; CSlot const& slot() const { return _slot; } private: uint8 _x; uint8 _y; CSlot _slot; }; inline void CAStarNode::updateMapPosition(CMapPosition& _mapPos) const { _mapPos.setUnitId(_x, _y); } inline bool CAStarNode::operator==(CAStarNode const& other) const { return _x==other._x && _y==other._y && _slot==other._slot; } inline bool CAStarNode::operator!=(CAStarNode const& other) const { return _x!=other._x || _y!=other._y || _slot!=other._slot; } inline bool CAStarNode::operator<(CAStarNode const& other) const { if (_x!=other._x) return _x(result)->serial(f); break; case White: result = new CWhiteCell(worldMap); static_cast(result)->serial(f); break; case SingleLayer: result = new CSingleLayerCell(worldMap); static_cast(result)->serial(f); break; case MultiLayer: result = new CMultiLayerCell(worldMap); static_cast(result)->serial(f); break; default: nlassert(false); nlwarning("Unknown type of cell %d to load, abort", type); return result; break; } // allow us to optimize access. f.serialCont(result->_TopologiesNodes); return result; } void CRootCell::save(NLMISC::IStream& f, CRootCell* cell) { if (dynamic_cast(cell) != NULL) { TCellType type = Compute; f.serialEnum(type); static_cast(cell)->serial(f); } else if (dynamic_cast(cell) != NULL) { TCellType type = White; f.serialEnum(type); static_cast(cell)->serial(f); } else if (dynamic_cast(cell) != NULL) { TCellType type = SingleLayer; f.serialEnum(type); static_cast(cell)->serial(f); } else if (dynamic_cast(cell) != NULL) { TCellType type = MultiLayer; f.serialEnum(type); static_cast(cell)->serial(f); } else { nlassert(false); nlwarning("Unknown type of cell to save, abort"); return; } f.serialCont(cell->_TopologiesNodes); } ////////////////////////////////////////////////////////////////////////////// // CComputeCell // ////////////////////////////////////////////////////////////////////////////// void CComputeCell::serial(NLMISC::IStream& f) { // Version // 0: initial version uint version = f.serialVersion(0); for (uint32 i=0; i<16*16; ++i) for (uint32 k=0; k<3; ++k) f.serial(_Grid[i][k]); } ////////////////////////////////////////////////////////////////////////////// // CSingleLayerCell // ////////////////////////////////////////////////////////////////////////////// bool CSingleLayerCell::_Initialized = false; uint16 CSingleLayerCell::_MaskMap[16]; void CSingleLayerCell::serial(NLMISC::IStream& f) { f.serialCheck((uint16)'SL'); uint i; for (i=0; i<16; ++i) f.serial(_Map[i]); f.serial(_SLinks); f.serial(_NLinks); f.serial(_ELinks); f.serial(_WLinks); if (f.isReading()) { delete _Topologies; delete _HeightMap; _Topologies = I16x16Layer::load(f); _HeightMap = I16x16Layer::load(f); } else { I16x16Layer::save(f, _Topologies); I16x16Layer::save(f, _HeightMap); } } ////////////////////////////////////////////////////////////////////////////// // CMultiLayerCell // ////////////////////////////////////////////////////////////////////////////// void CMultiLayerCell::serial(NLMISC::IStream& f) { f.serialCheck((uint16)'ML'); uint slot; for (slot=0; slot<3; ++slot) { // delete layer if any previously if (f.isReading()) { if (_Layers[slot] != NULL) delete _Layers[slot]->_HeightMap; delete _Layers[slot]; _Layers[slot] = NULL; } bool present = (_Layers[slot] != NULL); f.serial(present); if (present) { if (f.isReading()) { _Layers[slot] = new CCellLayer(); _Layers[slot]->_HeightMap = I16x16Layer::load(f); } else { I16x16Layer::save(f, _Layers[slot]->_HeightMap); } nlassert(_Layers[slot] != NULL); for (uint32 i=0; i<16*16; ++i) { f.serial(_Layers[slot]->_Layer[i]); f.serial(_Layers[slot]->_Topology[i]); } } } } ////////////////////////////////////////////////////////////////////////////// // CSuperCell // ////////////////////////////////////////////////////////////////////////////// void CSuperCell::serial(NLMISC::IStream& f) { // Version // 0: initial version uint version = f.serialVersion(0); if (f.isReading()) { for (uint32 i=0; i<16*16; ++i) { bool present; f.serial(present); if (_Grid[i] != NULL) delete _Grid[i]; if (present) _Grid[i] = CRootCell::load(f,_WorldMap); } } else { for (uint32 i=0; i<16*16; ++i) { bool present = (_Grid[i] != NULL); f.serial(present); if (present) CRootCell::save(f, _Grid[i]); } } } void CSuperCell::updateTopologyRef(CWorldMap* worldMap) { for (uint32 i=0; i<16*16; ++i) { if (_Grid[i]) _Grid[i]->updateTopologyRef (worldMap); } } void CSuperCell::countCells(uint& compute, uint& white, uint& simple, uint& multi, uint& other) const { for (uint32 i=0; i<16*16; ++i) { if (!_Grid[i]) continue; if (dynamic_cast(_Grid[i]) != NULL) ++white; else if (dynamic_cast(_Grid[i]) != NULL) ++simple; else if (dynamic_cast(_Grid[i]) != NULL) ++compute; else if (dynamic_cast(_Grid[i]) != NULL) ++multi; else ++other; } } ////////////////////////////////////////////////////////////////////////////// // CWorldMap // ////////////////////////////////////////////////////////////////////////////// void CWorldMap::getBounds(CMapPosition& min, CMapPosition& max) { uint i, j; uint mini = 256, maxi = 0, minj = 256, maxj = 0; for (i=0; i<256; ++i) { for (j=0; j<256; ++j) { if (_GridFastAccess[i*256+j]) { if (i < mini) mini = i; if (i > maxi) maxi = i; if (j < minj) minj = j; if (j > maxj) maxj = j; } } } min = CMapPosition(CMapCoord(minj, 0, 0), CMapCoord(mini, 0, 0) ); max = CMapPosition(CMapCoord(maxj+1, 0, 0), CMapCoord(maxi+1, 0, 0) ); } void CWorldMap::clear() { for (uint i=0;i<65536;i++) { if (_GridFastAccess[i]) { delete _GridFastAccess[i]; _GridFastAccess[i]=NULL; } } } void CWorldMap::serial(NLMISC::IStream &f) { f.serialCheck((uint32)'WMAP'); // Version // 0: initial version uint version = f.serialVersion(0); if (f.isReading()) { uint32 i; for (i=0;i<65536;i++) { bool present; f.serial(present); if (present) { CSuperCell *scell = _GridFastAccess[i]; if (!scell) _GridFastAccess[i] = scell =new CSuperCell(*this); f.serial(*scell); } } // made to update RootCell pointers in TTopologyRef .. for (i=0;i<65536;i++) { CSuperCell *scell = _GridFastAccess[i]; if (scell) scell->updateTopologyRef (this); } // made to calculate some random pos .. { CMapPosition min, max; getBounds(min, max); CMapPosition scan, scanline; NLMISC::CRandom random; for (scan = min; scan.y() != max.y(); scan = scan.stepCell(0, 1)) { for (scanline = scan; scanline.x() != max.x(); scanline = scanline.stepCell(1, 0)) { CRootCell *rootCell=getRootCell(scanline); if (!rootCell) continue; CMapPosition pos(scanline.x(),0xffff0000|scanline.y()); uint ind=0; uint maxTries=256; for (;ind<4 && maxTries>0;maxTries--) { CWorldPosition wpos; uint i= uint32(random.rand()) & 0xf; uint j= uint32(random.rand()) & 0xf; #ifdef NL_DEBUG nlassert(i<16 && j<16); #endif pos.setUnitId(i,j); CAIVector vecPos=CAIVector(pos); if (setWorldPosition (AITYPES::vp_auto, wpos, vecPos)) { #ifdef NL_DEBUG nlassert(wpos.getRootCell()==rootCell); nlassert(wpos.y()<=0 && wpos.x()>=0); #endif rootCell->setWorldPosition(wpos, ind); ind++; } } // if we have found some valid positions but not all, fill the array with the last pos found. if (ind<4 && ind>0) { while (ind<4) { rootCell->setWorldPosition(rootCell->getWorldPosition(ind-1), ind); ind++; } } } } } } else { for (uint32 i=0;i<65536;i++) { bool present = (_GridFastAccess[i]!=NULL); f.serial(present); if (present) { //nldebug("Save SuperCell %d/%d", i, j); f.serial(*(_GridFastAccess[i])); } } } } void CWorldMap::setFlagOnPosAndRadius(const CMapPosition &pos,float radius, uint32 flag) { float minx=pos.x()-radius; float maxx=pos.x()+radius; float miny=pos.y()-radius; float maxy=pos.y()+radius; const float radius2=radius*radius; for (float ty=miny;ty<=maxy;ty++) { const float dy=ty-pos.y(); for (float tx=minx;tx<=maxx;tx++) { const float dx=tx-pos.x(); if ((dy*dy+dx*dx)>radius2) continue; CRootCell *rootCell=getRootCell(CMapPosition((int)tx,(int)ty)); if (!rootCell) continue; rootCell->setFlag(flag); } } } void CWorldMap::countCells(uint &compute, uint &white, uint &simple, uint &multi, uint &other) const { for (uint32 i=0; i<65536; ++i) { if (_GridFastAccess[i]) _GridFastAccess[i]->countCells(compute, white, simple, multi, other); } // uint i, j; // for (i=0; i<256; ++i) // for (j=0; j<256; ++j) // if (_Grid[i][j] != NULL) // _Grid[i][j]->countCells(compute, white, simple, multi, other); } // CNeighbourhood CWorldMap::neighbours(const CWorldPosition &wpos) const { CNeighbourhood neighbs; const CCellLinkage lnks=wpos.getCellLinkage(); if (lnks.isSSlotValid()) { neighbs.set(CDirection::S); const CCellLinkage &nlnk = wpos.getPosS().getCellLinkage(); //CWorldPosition(wpos.getPosS(),lnks.NSlot())); if (nlnk.isESlotValid()) neighbs.set(CDirection::SE); if (nlnk.isWSlotValid()) neighbs.set(CDirection::SW); } if (lnks.isNSlotValid()) { neighbs.set(CDirection::N); const CCellLinkage &slnk = wpos.getPosN().getCellLinkage(); if (slnk.isESlotValid()) neighbs.set(CDirection::NE); if (slnk.isWSlotValid()) neighbs.set(CDirection::NW); } if (lnks.isESlotValid()) { neighbs.set(CDirection::E); const CCellLinkage &elnk = wpos.getPosE().getCellLinkage(); if (elnk.isSSlotValid()) neighbs.set(CDirection::SE); if (elnk.isNSlotValid()) neighbs.set(CDirection::NE); } if (lnks.isWSlotValid()) { neighbs.set(CDirection::W); const CCellLinkage &wlnk = wpos.getPosW().getCellLinkage(); if (wlnk.isSSlotValid()) neighbs.set(CDirection::SW); if (wlnk.isNSlotValid()) neighbs.set(CDirection::NW); } return neighbs; } // bool CWorldMap::customCheckDiagMove(const CWorldPosition &pos, const CDirection &direction, TAStarFlag denyFlags) const { H_AUTO(AI_WorldMap_move); // get straight links const CCellLinkage &lnk = pos.getCellLinkage(); switch (direction.getVal()) { case CDirection::SE: { { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isSSlotValid()) return false; tmpPos=tmpPos.getPosS(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isESlotValid()) return false; tmpPos=tmpPos.getPosE(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; } { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isESlotValid()) return false; tmpPos=tmpPos.getPosE(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isSSlotValid()) return false; // tmpPos=tmpPos.getPosS(); // if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) // return false; } return true; } case CDirection::NE: { { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isNSlotValid()) return false; tmpPos=tmpPos.getPosN(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isESlotValid()) return false; tmpPos=tmpPos.getPosE(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; } { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isESlotValid()) return false; tmpPos=tmpPos.getPosE(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isNSlotValid()) return false; // tmpPos=tmpPos.getPosN(); // if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) // return false; } return true; } case CDirection::NW: { { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isNSlotValid()) return false; tmpPos=tmpPos.getPosN(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isWSlotValid()) return false; tmpPos=tmpPos.getPosW(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; } { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isWSlotValid()) return false; tmpPos=tmpPos.getPosW(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isNSlotValid()) return false; // tmpPos=tmpPos.getPosN(); // if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) // return false; } return true; } case CDirection::SW: { { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isSSlotValid()) return false; tmpPos=tmpPos.getPosS(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isWSlotValid()) return false; tmpPos=tmpPos.getPosW(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; } { CWorldPosition tmpPos(pos); if (!tmpPos.getCellLinkage().isWSlotValid()) return false; tmpPos=tmpPos.getPosW(); if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) return false; if (!tmpPos.getCellLinkage().isSSlotValid()) return false; // tmpPos=tmpPos.getPosS(); // if ((tmpPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0) // return false; } return true; } break; default: break; } return false; } /* bool CWorldMap::customCheckDiagMove(const CWorldPosition &pos, const CDirection &direction, TAStarFlag denyFlags) const { H_AUTO(AI_WorldMap_move); // get straight links const CCellLinkage &lnk = pos.getCellLinkage(); switch (direction.getVal()) { case CDirection::SE: { if (!lnk.isSSlotValid()) return false; if (!lnk.isESlotValid()) return false; return ( pos.getPosS().getCellLinkage().isESlotValid() || pos.getPosE().getCellLinkage().isSSlotValid()); } case CDirection::NE: { if (!lnk.isNSlotValid()) return false; if (!lnk.isESlotValid()) return false; return ( pos.getPosN().getCellLinkage().isESlotValid() || pos.getPosE().getCellLinkage().isNSlotValid()); } case CDirection::NW: { if (!lnk.isNSlotValid()) return false; if (!lnk.isWSlotValid()) return false; return ( pos.getPosN().getCellLinkage().isWSlotValid() || pos.getPosW().getCellLinkage().isNSlotValid()); } case CDirection::SW: { if (!lnk.isSSlotValid()) return false; if (!lnk.isWSlotValid()) return false; return ( pos.getPosS().getCellLinkage().isWSlotValid() || pos.getPosW().getCellLinkage().isSSlotValid()); } } return false; } */ // bool CWorldMap::move(CWorldPosition &pos, const CDirection &direction) const { H_AUTO(AI_WorldMap_move); // get straight links const CCellLinkage &lnk = pos.getCellLinkage(); switch (direction.getVal()) { case CDirection::S: { if (lnk.isSSlotValid()) { pos.stepS(); return true; } return false; } case CDirection::SE: { if (lnk.isSSlotValid()) { const CWorldPosition temp(pos.getPosS()); if (temp.getCellLinkage().isESlotValid()) { temp.setPosE(pos); return true; } } if (lnk.isESlotValid()) { const CWorldPosition temp(pos.getPosE()); if (temp.getCellLinkage().isSSlotValid()) { temp.setPosS(pos); return true; } } return false; } case CDirection::E: { if (lnk.isESlotValid()) { pos.stepE(); return true; } return false; } case CDirection::NE: { if (lnk.isESlotValid()) { const CWorldPosition temp(pos.getPosE()); if (temp.getCellLinkage().isNSlotValid()) { temp.setPosN(pos); return true; } } if (lnk.isNSlotValid()) { const CWorldPosition temp(pos.getPosN()); if (temp.getCellLinkage().isESlotValid()) { temp.setPosE(pos); return true; } } return false; } case CDirection::N: { if (lnk.isNSlotValid()) { pos.stepN(); return true; } return false; } case CDirection::NW: { if (lnk.isWSlotValid()) { const CWorldPosition temp(pos.getPosW()); if (temp.getCellLinkage().isNSlotValid()) { temp.setPosN(pos); return true; } } if (lnk.isNSlotValid()) { const CWorldPosition temp(pos.getPosN()); if (temp.getCellLinkage().isWSlotValid()) { temp.setPosW(pos); return true; } } return false; } case CDirection::W: { if (lnk.isWSlotValid()) { pos.stepW(); return true; } return false; } case CDirection::SW: { if (lnk.isSSlotValid()) { const CWorldPosition temp(pos.getPosS()); if (temp.getCellLinkage().isWSlotValid()) { temp.setPosW(pos); return true; } } if (lnk.isWSlotValid()) { const CWorldPosition temp(pos.getPosW()); if (temp.getCellLinkage().isSSlotValid()) { temp.setPosS(pos); return true; } } return false; } default: break; } return false; } // bool CWorldMap::moveSecure(CWorldPosition &pos, const CDirection &direction, uint16 maskFlags) const { H_AUTO(AI_WorldMap_moveSecure); // get straight links uint16 pflags = pos.getFlags(); switch (direction.getVal()) { case CDirection::S: { return pos.moveS(); } case CDirection::SE: { CWorldPosition p1(pos), p2(pos); if (p1.moveS() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p1.moveE() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p2.moveE() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p2.moveS() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p1 == p2) { pos = p1; return true; } break; } case CDirection::E: { return pos.moveE(); } case CDirection::NE: { CWorldPosition p1(pos), p2(pos); if (p1.moveN() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p1.moveE() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p2.moveE() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p2.moveN() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p1 == p2) { pos = p1; return true; } break; } case CDirection::N: { return pos.moveN(); } case CDirection::NW: { CWorldPosition p1(pos), p2(pos); if (p1.moveN() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p1.moveW() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p2.moveW() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p2.moveN() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p1 == p2) { pos = p1; return true; } break; } case CDirection::W: { return pos.moveW(); } case CDirection::SW: { CWorldPosition p1(pos), p2(pos); if (p1.moveS() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p1.moveW() && (((p1.getFlags()^pflags)&maskFlags) == 0) && p2.moveW() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p2.moveS() && (((p2.getFlags()^pflags)&maskFlags) == 0) && p1 == p2) { pos = p1; return true; } break; } default: break; } return false; } // bool CWorldMap::moveDiagTestBothSide(CWorldPosition &pos, const CDirection &direction) const { H_AUTO(AI_WorldMap_move); // get straight links const CCellLinkage &lnk = pos.getCellLinkage(); switch (direction.getVal()) { case CDirection::S: { if (lnk.isSSlotValid()) { pos.stepS(); return true; } return false; } case CDirection::SE: { if (!lnk.isSSlotValid() || !lnk.isESlotValid()) return false; CSlot eSlot=pos.getPosS().getCellLinkage().ESlot(); if (!eSlot.isValid()) return false; const CWorldPosition temp(pos.getPosE()); if (temp.getCellLinkage().SSlot()!=eSlot) return false; temp.setPosS(pos); return true; } case CDirection::E: { if (lnk.isESlotValid()) { pos.stepE(); return true; } return false; } case CDirection::NE: { if (!lnk.isNSlotValid() || !lnk.isESlotValid()) return false; CSlot eSlot=pos.getPosN().getCellLinkage().ESlot(); if (!eSlot.isValid()) return false; const CWorldPosition temp(pos.getPosE()); if (temp.getCellLinkage().NSlot()!=eSlot) return false; temp.setPosN(pos); return true; } case CDirection::N: { if (lnk.isNSlotValid()) { pos.stepN(); return true; } return false; } case CDirection::NW: { if (!lnk.isNSlotValid() || !lnk.isWSlotValid()) return false; CSlot wSlot=pos.getPosN().getCellLinkage().WSlot(); if (!wSlot.isValid()) return false; const CWorldPosition temp(pos.getPosW()); if (temp.getCellLinkage().NSlot()!=wSlot) return false; temp.setPosN(pos); return true; } case CDirection::W: { if (lnk.isWSlotValid()) { pos.stepW(); return true; } return false; } case CDirection::SW: { if (!lnk.isSSlotValid() || !lnk.isWSlotValid()) return false; CSlot wSlot=pos.getPosS().getCellLinkage().WSlot(); if (!wSlot.isValid()) return false; const CWorldPosition temp(pos.getPosW()); if (temp.getCellLinkage().SSlot()!=wSlot) return false; temp.setPosS(pos); return true; } default: break; } return false; } void areCompatiblesWithoutStartRestriction(const CWorldPosition &startPos, const CWorldPosition& endPos, const TAStarFlag &denyflags, CCompatibleResult &res, bool allowStartRestriction) { res.setValid(false); if (&startPos == NULL) { nlwarning("Invalid startPos (NULL)"); return; } if (&endPos == NULL) { nlwarning("Invalid endPos (NULL)"); return; } const CTopology &startTopoNode=startPos.getTopologyRef().getCstTopologyNode(); const CTopology &endTopoNode=endPos.getTopologyRef().getCstTopologyNode(); TAStarFlag startFlag=(TAStarFlag)(startTopoNode.getFlags()&WaterAndNogo); // if (!allowStartRestriction) startFlag=Nothing; for (TAStarFlag possibleFlag=Nothing;possibleFlag<=WaterAndNogo;possibleFlag=(TAStarFlag)(possibleFlag+2)) // tricky !! -> to replace with a defined list of flags to checks. { const uint32 incompatibilityFlags=(possibleFlag&(denyflags&~startFlag))&WaterAndNogo; if (incompatibilityFlags) continue; const uint32 startMasterTopo=startTopoNode.getMasterTopo(possibleFlag); const uint32 endMasterTopo=endTopoNode.getMasterTopo(possibleFlag); if ( (startMasterTopo^endMasterTopo)!=0 || startMasterTopo==~0) // if not same masterTopo or invalid masterTopo then bypass .. continue; res.set(possibleFlag, startMasterTopo); res.setValid(); if (((possibleFlag&denyflags)&WaterAndNogo)==0) // it was the optimal case ? break; } } ////////////////////////////////////////////////////////////////////////////// // Path finding // ////////////////////////////////////////////////////////////////////////////// //#define CHECK_HEAP std::map MapAStarNbSteps; uint LastAStarNbSteps = 0; NLMISC_CATEGORISED_COMMAND(ais, dumpAStarSteps, "Dump the distribution of A* number of steps", "") { log.displayNL( "Distribution of the %u nb steps:", MapAStarNbSteps.size() ); log.displayNL( "NbSteps\tNbOccurrences", MapAStarNbSteps.size() ); for( std::map::const_iterator first=MapAStarNbSteps.begin(), last=MapAStarNbSteps.end(); first!=last; ++first ) { log.displayNL( "%u\t%u", first->first, first->second ); } return true; } bool CWorldMap::findAStarPath(CWorldPosition const& start, CWorldPosition const& end, std::vector& path, TAStarFlag denyflags) const { H_AUTO(findAStarPath1); // Clear destination path path.clear(); // Check start position validity if (!start.isValid()) { _LastFASPReason = FASPR_INVALID_START_POS; return false; } // Check end position validity if (!end.isValid()) { _LastFASPReason = FASPR_INVALID_END_POS; return false; } // Get start and end topologies CTopology::TTopologyRef startTopo = start.getTopologyRef(); CTopology::TTopologyRef endTopo = end.getTopologyRef(); // Get associated topology nodes CTopology const& startTopoNode = startTopo.getCstTopologyNode(); CTopology const& endTopoNode = endTopo.getCstTopologyNode(); // Check start point if (!startTopo.isValid()) { _LastFASPReason = FASPR_INVALID_START_TOPO; return false; } // Check end point if (!endTopo.isValid()) { _LastFASPReason = FASPR_INVALID_END_TOPO; return false; } // Check compatibility of start and end points depending on flags to avoid RYAI_MAP_CRUNCH::CCompatibleResult res; areCompatiblesWithoutStartRestriction(start, end, denyflags, res, true); if (!res.isValid()) { _LastFASPReason = FASPR_INCOMPATIBLE_POSITIONS; return false; } // Get flags to use to compute the path TAStarFlag movementFlags = res.movementFlags(); // Get the master topology inside which to compute the path (reminder: no path between different master topo) uint32 choosenMasterTopo=res.choosenMasterTopo(); // A list of A* nodes vector nodes; // List of visited topologies, with associated node in 'nodes' vector map visited; // The heap used to store A* nodes // :TODO: Check if STL heap is not better suited, or if another data structure would be more useful in AIS CHeap heap; // Get end position CVector const& endPoint = endTopo.getCstTopologyNode().Position; // Create a heap node for the start point CAStarHeapNode hnode(startTopo, 0xffffffff, 0.0f, true); // Push it in the node list nodes.push_back(hnode); // Take it as first father uint father = (uint)nodes.size()-1; // Add start topology to visited nodes (father holds start topo node index for the moment) visited.insert(make_pair(startTopo, father)); // Push start node in the heap with a zero cost heap.push(0.0f, father); // Boolean to notify that end point has been reached bool found = false; #ifdef CHECK_HEAP static uint32 maxHeap = 65535; static uint32 maxHeapMeasure = 0; #endif uint nbHeapSteps = 0; while (!heap.empty()) { #ifdef CHECK_HEAP if (heap.size()>maxHeap) // if too much calculs, not found (to remove when debugged). break; #endif ++nbHeapSteps; // Get best node (popping it) father = ~0; // :TODO: Remove that useless statement (since do while first loop ALWAYS overwrite it) do { father = heap.pop(); } while (!nodes[father].isOpened() && !heap.empty()); if (father==~0) break; // Mark current node as closed hnode.setOpen(false); // Make best node the current one hnode = nodes[father]; // Get the current node itself CTopology::TTopologyRef const& current = hnode.getRef(); // If we reached the end node, stop search if (current==endTopo) { found=true; break; } // Get current node topology CTopology const& ctp = current.getCstTopologyNode(); // Get g(n) for current node float dist = hnode.getDistance(); // Examine each neighbour of the current node for (vector::const_iterator it=ctp.Neighbours.begin(), itEnd=ctp.Neighbours.end();it!=itEnd;++it) { // Get the neighbour topology node CTopology::CNeighbourLink const& neighbourLink = (*it); CTopology::TTopologyRef const& next = neighbourLink.getTopologyRef(); // If it's not in the same master topo skip it if (next.getCstTopologyNode().getMasterTopo(movementFlags)!=choosenMasterTopo) continue; // Compute neighbour node g(n) float distance = dist + neighbourLink.getDistance(); uint child; // Check if node has already been visited map::iterator itv = visited.find(next); if (itv!=visited.end()) { // Assume child is that node child = (*itv).second; // If that node's previous distance is better than the new one skip it if (nodes[child].getDistance() <= distance) continue; // Close the old node nodes[child].setOpen(false); // Remove it from visited visited.erase(itv); } // Create a new node for that cell child = (uint)nodes.size(); nodes.push_back(CAStarHeapNode(next, father, distance, true)); // Compute h(n) as an euclidian distance heuristic float heuristic = (endPoint-next.getCstTopologyNode().Position).norm(); // Add node to heap with a computed f(n)=g(n)+h(n) heap.push(distance + heuristic, child); // Add node to visited visited.insert(make_pair(next, child)); } } #ifdef CHECK_HEAP if (heap.size()>maxHeapMeasure) { maxHeapMeasure=heap.size(); } #endif ++MapAStarNbSteps[nbHeapSteps]; LastAStarNbSteps = nbHeapSteps; #ifdef NL_DEBUG nlassert(found); #else if (!found) { nlwarning("(!!Appeler StepH!!)Path not found from %s : %d to %s : %d", start.toString().c_str(), start.slot(), end.toString().c_str(), end.slot()); } #endif // If not found, return error if (!found) { _LastFASPReason = FASPR_NOT_FOUND; return false; } // Backtrack path while (father != 0xffffffff) { CAStarHeapNode const& node = nodes[father]; path.push_back(node.getRef()); father = node.getFather(); } // Reverse path container std::reverse(path.begin(), path.end()); _LastFASPReason = FASPR_NO_ERROR; return true; } // Finds an A* path bool CWorldMap::findAStarPath(const CTopology::TTopologyId &start, const CTopology::TTopologyId &end, CAStarPath &path, TAStarFlag denyflags) const { H_AUTO(findAStarPath2) path._TopologiesPath.clear(); CTopology::TTopologyRef startTopo = getTopologyRef(start); CTopology::TTopologyRef endTopo = getTopologyRef(end); // if not found start point or end point, abort if (!startTopo.isValid() || !endTopo.isValid()) return false; vector nodes; map visited; // topology + node index in vector CHeap heap; const CVector &endPoint = endTopo.getCstTopologyNode().Position; // add current to heap CAStarHeapNode hnode(startTopo,0xffffffff,0.0f,true); nodes.push_back(hnode); uint father = (uint)nodes.size()-1; // add current to visited nodes visited.insert(make_pair(startTopo, father)); heap.push(0.0f, father); bool found=false; while (!heap.empty()) { // pop best node father = heap.pop(); hnode = nodes[father]; const CTopology::TTopologyRef ¤t = hnode.getRef(); // if reached end node, leave if (current==endTopo) { found=true; break; } const CTopology &ctp = current.getCstTopologyNode(); float dist = hnode.getDistance(); for (uint i=0; i::iterator itv = visited.find(next); if (itv != visited.end() && nodes[(*itv).second].getDistance() <= distance) continue; // compute heuristic float heuristic = distance + (endPoint-ntp.Position).norm(); // setup node CAStarHeapNode cnode(next,father,distance,true); uint child; // setup node if (itv == visited.end()) { // if node is not open nor closed, create an entry child = (uint)nodes.size(); nodes.push_back(cnode); } else { // else recover previous entry -- reopen node child = (*itv).second; nodes[child]=cnode; } // add node to visited and to heap heap.push(heuristic, child); visited.insert(make_pair(next, child)); } } // if not found, return error if (!found) return false; // backtrack path while (father != 0xffffffff) { const CAStarHeapNode& node=nodes[father]; path._TopologiesPath.push_back(node.getRef()); father = node.getFather(); } // reverse path reverse(path._TopologiesPath.begin(), path._TopologiesPath.end()); return true; } // This whole routine MUST be optimized !! Its slow .. (to much). bool CWorldMap::findInsideAStarPath(CWorldPosition const& start, CWorldPosition const& end, std::vector& stepPath, TAStarFlag denyflags) const { H_AUTO(findInsideAStarPath); // Check start and end position validity if (!start.isValid()) { _LastFIASPReason = FIASPR_INVALID_START_POS; return false; } if (!end.isValid()) { _LastFIASPReason = FIASPR_INVALID_END_POS; return false; } // Verify that they are in the same topology if (start.getTopologyRef()!=end.getTopologyRef()) { _LastFIASPReason = FIASPR_DIFFERENT_TOPO; return false; } // A list of A* nodes vector nodes; // List of visited nodes, with associated node index in 'nodes' vector map visited; // The heap used to store A* nodes // :TODO: Check if STL heap is not better suited, or if another data structure would be more useful in AIS CHeap heap; // Get end point CVector endPoint = end.toVectorD(); // Build a node for the end point CAStarNode endId(end); // Build a node for the start point CAStarNode startNode(start); // Create a heap node for the start point and push it in the node list nodes.push_back(CInsideAStarHeapNode(startNode, 0xffffffff, CDirection(), 0.f, true)); // Take it as first father uint father = (uint)nodes.size()-1; // Add start node to visited nodes (father holds start node index for the moment) visited.insert(make_pair(startNode, father)); // Push start node in the heap with a zero cost heap.push(0.0f, father); // Boolean to notify that end point has been reached bool found = false; while (!heap.empty()) { // Get best node (popping it) father = heap.pop(); // Get associated heap node CInsideAStarHeapNode& hnode = nodes[father]; // Get associated A* node CAStarNode const& current = hnode.getNode(); // If we reached the end node, stop search if (current==endId) { found=true; break; } // Get g(n) for current node float dist = hnode.getDistance(); // Get current node slot CSlot slot = current.slot(); // Compute a map position CMapPosition pos = start; // The map has the same cell than the start pos (coz in same topo) current.updateMapPosition(pos); // Just update the unit id // For each neighbour (8 directions) CNeighbourhood neighbourhood = neighbours(getWorldPosition(pos,slot)); for (uint i=0; i<8; ++i) { // Compute a CDirection CDirection dir((CDirection::TDirection)i); // If neighbour in that direction is not valid skip it if (!neighbourhood.isValid(dir)) continue; // :TODO: Continue documentation // If we cannot move in that direction skip it CWorldPosition mv(getWorldPosition(pos, slot)); if (!moveDiagTestBothSide(mv, dir)) continue; // If that new point is not in the same cell skip it if (!mv.hasSameFullCellId(start)) continue; // If that point's flags are not compatible skip it if ((denyflags & mv.getTopologyRef().getCstTopologyNode().getFlags()) != 0) continue; // Build an A* node CAStarNode next(mv); // Compute g(n) (diagonal) float distance = dist + (((i & 1) != 0) ? 1.4142f : 1.0f); // If node has already been visited and previous distance was better skip it map::iterator itv = visited.find(next); if ( itv != visited.end() && nodes[(*itv).second].getDistance() <= distance ) continue; uint child; // If node has already been visited update it if (itv!=visited.end()) { child = (*itv).second; nodes[child] = CInsideAStarHeapNode(next, father, dir, distance, true); } // Else create a new node else { child = (uint)nodes.size(); nodes.push_back(CInsideAStarHeapNode(next, father, dir, distance, true)); } // Compute h(n) as an euclidian distance heuristic float heuristic = (endPoint-mv.toVectorD()).norm(); // Add node to heap with a computed f(n)=g(n)+h(n) heap.push(distance + heuristic, child); // Add node to visited visited.insert(make_pair(next, child)); } } #ifdef NL_DEBUG nlassert(found); #endif // If not found, return error if (!found) { _LastFIASPReason = FIASPR_NOT_FOUND; return false; } stepPath.clear(); // Backtrack path while (father != 0xffffffff) { if (nodes[father].getFather() != 0xffffffff) stepPath.push_back(nodes[father].getDirection()); father = nodes[father].getFather(); } // Reverse path container std::reverse(stepPath.begin(), stepPath.end()); _LastFIASPReason = FIASPR_NO_ERROR; return true; } ////////////////////////////////////////////////////////////////////////////// // // ////////////////////////////////////////////////////////////////////////////// bool CWorldMap::moveTowards(CWorldPosition& pos, CTopology::TTopologyRef const& topology) const { CGridDirectionLayer const* layer = getGridDirectionLayer(pos, topology); if (!layer) return false; CDirection motion=layer->getDirection(pos); if (!motion.isValid()) return false; return move(pos,motion); } // Moves according a to a given path, returns false if failed bool CWorldMap::move(CWorldPosition &pos, CAStarPath &path, uint ¤tstep) const { if (currentstep >= path._TopologiesPath.size()) return false; CTopology::TTopologyRef cid(pos); if (!cid.isValid()) return false; if (cid==path._TopologiesPath[currentstep]) { ++currentstep; if (currentstep==path._TopologiesPath.size()) return false; } return moveTowards(pos, path._TopologiesPath[currentstep]); } // Moves from a position to another bool CWorldMap::move(CWorldPosition& pos, CMapPosition const& end, TAStarFlag const denyFlags) const { CWorldPosition tempPos(pos); BOMB_IF((tempPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlags)!=0, "Error in CWorldMap::mode, invalid flag "<clearHeightMap(); } } } // checks motion layers void CWorldMap::checkMotionLayer() { CMapPosition min, max; getBounds(min, max); uint compute = 0, white = 0, simple = 0, multi = 0, other = 0; countCells(compute, white, simple, multi, other); uint total = compute+white+simple+multi+other; uint compCells = 0; CMapPosition scan, scanline; CTimeEstimator timeest(total); for (scan = min; scan.yCoord() != max.yCoord(); scan = scan.stepCell(0, 1)) { for (scanline = scan; scanline.x() != max.x(); scanline = scanline.stepCell(1, 0)) { CRootCell* rootCell=getRootCell(scanline); if (!rootCell) continue; timeest.step("checkMotionLayer"); vector &topologies = rootCell->getTopologiesNodes(); // move from any point to the current topology uint i; for (i=0; i neighbours; // uint16 toflags = topology.Flags; uint j; for (j=0; jdump(); } } } } } // void CWorldMap::buildMasterTopo(bool allowWater, bool allowNogo) { nlinfo("buildMasterTopo"); CMapPosition min, max; getBounds(min, max); CMapPosition scan, scanline; uint masterTopo = 0; uint totalTopos = 0; for (scan = min; scan.y() != max.y(); scan = scan.stepCell(0, 1)) { for (scanline = scan; scanline.x() != max.x(); scanline = scanline.stepCell(1, 0)) { CRootCell *cell = getRootCell(scanline); if (cell == NULL) continue; vector &topos = cell->getTopologiesNodes(); uint i; for (i=0; i tovisit; // set mastertopo id tp.getMasterTopoRef(allowWater, allowNogo) = masterTopo; tovisit.insert(tp.Id); while (!tovisit.empty()) { CTopology& t = getTopologyNode(*(tovisit.begin())); tovisit.erase(tovisit.begin()); uint j; for (j=0; j visited; uint totalTopos = 0; for (scan = min; scan.y() != max.y(); scan = scan.stepCell(0, 1)) { for (scanline = scan; scanline.x() != max.x(); scanline = scanline.stepCell(1, 0)) { const CRootCell *cell = getRootCellCst(scanline); if (cell == NULL) continue; const vector &topos = cell->getTopologiesNodes(); uint i; for (i=0; i tovisit; tovisit.insert(topoid); visited.insert(topoid); while (!tovisit.empty()) { CTopology::TTopologyId id = *(tovisit.begin()); tovisit.erase(tovisit.begin()); const CTopology &t = getTopologyNode(id); uint j; for (j=0; jisSlotUsed(mapPos, CSlot(s))) continue; CSlot sslot=CSlot(s); sint32 sh = cell->getMetricHeight(CWorldPosition(cell, mapPos, sslot)); sint32 dist = z-sh; dist = dist<0?-dist:dist; if (dist < minDistZ) { nlassert(dist>=0); minDistZ = dist; bestZ = sh; bestSlot = sslot; } } if (!bestSlot.isValid()) { wpos = CWorldPosition(cell,mapPos,bestSlot,true); return false; } wpos = CWorldPosition(cell,mapPos,bestSlot); // if (simulateBug(5)) // { // if (minDistZ > 2000) // { // return false; // } // } // else { // :KLUDGE: Water hack // If error is too big and player is either not in water or under slot (ie not floating above slot at surface) if (minDistZ > 2000 && ((wpos.getFlags()&RYAI_MAP_CRUNCH::Water)==0 || zisSlotUsed(mapPos, CSlot(s))) continue; CSlot sslot=CSlot(s); double sh = ((double)cell->getMetricHeight(CWorldPosition(cell, mapPos, sslot)))/1000.0; double dist = fabs(z-sh); if (dist < minDistZ) { nlassert(dist>=0); minDistZ = dist; bestZ = sh; bestSlot = sslot; } } if (!bestSlot.isValid()) { wpos=CWorldPosition(cell,mapPos,bestSlot,true); return false; } if (minDistZ > 2.000) { // nldebug("Setting a WorldPosition too far from specified z: x=%d y=%d z=%f slotz=%f", pos.x(), pos.y(), z, bestZ); wpos = CWorldPosition(cell,mapPos,bestSlot,true); return false; } wpos=CWorldPosition(cell,mapPos,bestSlot); return true; } }