// 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 . /* TODO: - Deal with invisibility masks - Deal with biasing - add a solution for split() if all of the entities in the group are super-imposed */ //----------------------------------------------------------------------------- // includes //----------------------------------------------------------------------------- #include "stdpch.h" #include "nel/misc/types_nl.h" #include "r2_vision.h" #ifdef NL_DEBUG // #define ENABLE_TESTS #endif #include "game_share/sadge_tests.h" #include "game_share/tick_event_handler.h" //#undef TEST //#define TEST(txt_and_args) nldebug txt_and_args //----------------------------------------------------------------------------- // NLMISC variables //----------------------------------------------------------------------------- NLMISC::CVariable VerboseVisionDelta("r2vision","VerboseVisionDelta","enable verbose logging of vision changes",false, 0, true); NLMISC::CVariable VisionResetDuration("r2vision","VisionResetDuration","the number of updates to be skipped when reseting player vision",10, 0, true); //----------------------------------------------------------------------------- // namespace R2_VISION //----------------------------------------------------------------------------- namespace R2_VISION { //----------------------------------------------------------------------------- // Constants //----------------------------------------------------------------------------- // the maximum radius that we consider correct for a vision group static const uint32 MaxVisionGroupRadius= 50*1000; // 50 meters static const uint32 MaxVisionGroupDiameter= 2*MaxVisionGroupRadius; // the maximum number of viewers permitted in a vision group static const uint32 MaxViewersPerGroup= 100; // max viewer dist beyond which we start to split the group into sub groups static const uint32 MinVisionGroupSplitDiameter= ((MaxVisionGroupDiameter*5)/4); // max dist beyond which entities are never seen static const uint32 MaxVisionDist= 250*1000; // 250 meter static const uint32 VisionBucketShift= 15; // divide by 32768 static const uint32 NumVisionBuckets= 1+(MaxVisionDist>>VisionBucketShift); static const uint32 SmallestBucketSize= 1<ydist)? (xdist+(ydist>>1)): (ydist+(xdist>>1)); } inline bool visionGroupsOverlap(const CVisionGroup* va, const CVisionGroup* vb) { // if either of the vision groups are empty return false if (va==NULL || vb==NULL || va->numViewers()==0 || vb->numViewers()==0) return false; // if the vision groups don't share the same vision level then return false if (va->getVisionLevel() != vb->getVisionLevel()) return false; // calculate the minima of x and Y coordinates from the 2 groups uint32 combinedXMin= std::min(va->xMin(),vb->xMin()); uint32 combinedYMin= std::min(va->yMin(),vb->yMin()); uint32 combinedXMax= std::max(va->xMax(),vb->xMax()); uint32 combinedYMax= std::max(va->yMax(),vb->yMax()); // if the combined groups' bounding space would be too large then don't bother looking any further if (quickDist( combinedXMin, combinedYMin, combinedXMax, combinedYMax ) > MaxVisionGroupDiameter) return false; // the overlap is confirmed if the sum of the players in the 2 groups is small enough if (va->numViewers() + vb->numViewers() < MaxViewersPerGroup) return true; // calculate the distance from extreme x min to extreme x max and extreme y min to exetrem y max uint32 extemeXdist= combinedXMax - combinedXMin; uint32 extemeYdist= combinedYMax - combinedYMin; uint32 xDistA= (va->xMax() - va->xMin()); uint32 yDistA= (va->yMax() - va->yMin()); uint32 xDistB= (vb->xMax() - vb->xMin()); uint32 yDistB= (vb->yMax() - vb->yMin()); // calculate the sum of the 2 vision group lengths in each of x and y uint32 sumXdist= xDistA + xDistB; uint32 sumYdist= yDistA + yDistB; // we consider that a real overlap occurs when both extreme values are < 3/4 of the equivalent sum values if ( (4*extemeXdist<=3*sumXdist) && (4*extemeYdist<=3*sumYdist) ) return true; // we consider the vision groups don't overlap return false; } //----------------------------------------------------------------------------- // METHODS CUniverse //----------------------------------------------------------------------------- void CUniverse::createInstance(uint32 aiInstance, uint32 groupId) { // just ignore attempts to create the ~0u instance if (aiInstance==~0u) { return; } // ensure that the new AIInstance value is reasonable & increase _Instances vector size if required BOMB_IF(aiInstance>65535,"Failed to create Instance with implausible AIInstance value",return); if (_Instances.size()<=aiInstance) { nlinfo("Increasing Universe::AIInstance vector size to: %d",aiInstance+1); _Instances.resize(aiInstance+1); } NLMISC::CSmartPtr& theInstance= _Instances[aiInstance]; // allocating the new instance if (theInstance==NULL) { theInstance= new CInstance(this,groupId); nlinfo("Allocating new AIInstance: %d at address: %p",aiInstance,&*theInstance); } } void CUniverse::removeInstance(uint32 aiInstance) { // just ignore attempts to remove the ~0u instance if (aiInstance==~0u) { return; } // make sure the instance exists DROP_IF(aiInstance>=_Instances.size()||_Instances[aiInstance]==NULL, NLMISC::toString("Ignoring attempt to remove non-existant instance: %d",aiInstance),return); // allow the instance to do housekeeping before being destroyed _Instances[aiInstance]->release(); // destroy the instance _Instances[aiInstance]=NULL; } void CUniverse::removeInsancesByGroup(uint32 groupId) { // iterate over complete vector of instances releasing any that match the given group id for (uint32 i=0;i<_Instances.size();++i) { if (_Instances[i]!=NULL && _Instances[i]->getGroupId()==groupId) { removeInstance(i); } } } void CUniverse::addEntity(TDataSetRow dataSetRow,uint32 aiInstance,sint32 x,sint32 y,TInvisibilityLevel invisibilityLevel,bool isViewer) { H_AUTO(CUniverse_addEntity) // strip junk from data set row to leave a pure index value uint32 row= dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset row=0",return); BOMB_IF(row>(1<<20),NLMISC::toString("Ignoring entity with implausible row value: %d",row),return); // delegate to workhorse routine (converting y coordinate to +ve axis) _addEntity(dataSetRow,aiInstance,x,-y,invisibilityLevel,isViewer); } void CUniverse::forceRefreshVision(TDataSetRow dataSetRow) { uint32 row = dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset row=0",return); BOMB_IF(row>(1<<20),NLMISC::toString("Ignoring entity with implausible row value: %d",row),return); SUniverseEntity& theEntity= _Entities[row]; // if the entity was already allocated then remove it if (theEntity.AIInstance == ~0u) { return; } if ( theEntity.ViewerRecord) { uint32 x = theEntity.ViewerRecord->getX(); uint32 y = theEntity.ViewerRecord->getY(); TInvisibilityLevel invisibilityLevel = theEntity.ViewerRecord->getVisionLevel(); uint32 aiInstance = theEntity.AIInstance; _addEntity(dataSetRow,aiInstance, static_cast(x), static_cast(y),invisibilityLevel, true); } } void CUniverse::forceResetVision(TDataSetRow dataSetRow) { uint32 row = dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset row=0",return); BOMB_IF(row>(1<<20),NLMISC::toString("Ignoring entity with implausible row value: %d",row),return); // get the pointer to the entity record and ensure that the entity is a viewer SUniverseEntity& theEntity= _Entities[row]; BOMB_IF(theEntity.ViewerRecord==NULL,"IGNORING ResetVision for non-viewer entity"+dataSetRow.toString(),return); // if we're in verbose logging mode then do some logging... if (VerboseVisionDelta) { nlinfo("FRR- Force Reset Vision received for entitiy: %s",dataSetRow.toString().c_str()); } // flag the ViewerRecord to force a vision reset theEntity.ViewerRecord->forceResetVision(); } void CUniverse::removeEntity(TDataSetRow dataSetRow) { H_AUTO(CUniverse_removeEntity) // strip junk from data set row to leave a pure index value uint32 row= dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset=0",return); BOMB_IF(row>=_Entities.size(),NLMISC::toString("Ignoring attempt to remove entity with invalid row value: %d",row),return); // delegate to workhorse routine _removeEntity(dataSetRow); } SUniverseEntity* CUniverse::getEntity(TDataSetRow dataSetRow) { return getEntity(dataSetRow.getIndex()); } SUniverseEntity* CUniverse::getEntity(uint32 row) { BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset=0",return NULL); BOMB_IF(row>=_Entities.size(),NLMISC::toString("Attempting to get hold of entity with invalid row value: %d",row),return NULL); return &_Entities[row]; } void CUniverse::setEntityPosition(TDataSetRow dataSetRow,sint32 x,sint32 y) { H_AUTO(CUniverse_setEntityPosition) // strip junk from data set row to leave a pure index value uint32 row= dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with dataset=0",return); BOMB_IF(row>=_Entities.size(),NLMISC::toString("Ignoring attempt to set entity position with invalid row value: %d",row),return); // delegate to workhorse routine (converting y coordinate to +ve axis) _setEntityPosition(dataSetRow,x,-y); } void CUniverse::setEntityPositionDelayed(TDataSetRow dataSetRow,sint32 x,sint32 y,uint32 ticks) { H_AUTO(CUniverse_setEntityPositionDelayed) nlinfo("adding delayed entity position to the list: %s (%d,%d) @ time: %d",dataSetRow.toString().c_str(),x,y,ticks); _DelayedPositions.push_back(SDelayedPlayerPosition(ticks,dataSetRow,x,y)); } void CUniverse::teleportEntity(TDataSetRow dataSetRow,uint32 aiInstance,sint32 x,sint32 y,TInvisibilityLevel invisibilityLevel) { H_AUTO(CUniverse_teleportEntity) // strip junk from data set row to leave a pure index value uint32 row= dataSetRow.getIndex(); // ensure that the row value is valid BOMB_IF(row==0,"Illegal attempt to manipulate entity with datasetrow=0",return); BOMB_IF(row>=_Entities.size(),NLMISC::toString("Ignoring attempt to set entity position with invalid row value: %d",row),return); // ensure that the new AIInstance exists BOMB_IF(aiInstance!=~0u && (aiInstance>=_Instances.size() || _Instances[aiInstance]==NULL), NLMISC::toString("ERROR: Failed to add entity %d to un-initialised instance: %d",row,aiInstance),aiInstance=~0u); // delegate to workhorse routine (converting y coordinate to +ve axis) _teleportEntity(dataSetRow,aiInstance,x,-y,invisibilityLevel); } void CUniverse::setEntityInvisibilityInfo(TDataSetRow dataSetRow,uint32 whoSeesMe) { // get a handle to the entity SUniverseEntity& theEntity= _Entities[dataSetRow.getIndex()]; // tell the instance that the entity has changed visiblity if (theEntity.AIInstance<_Instances.size()) { NLMISC::CSmartPtr& theInstance= _Instances[theEntity.AIInstance]; // extract the invisibility level from the whoSeesMe value TInvisibilityLevel invisibilityLevel= (TInvisibilityLevel)(whoSeesMe&((1<setEntityInvisibility(theEntity.InstanceIndex, invisibilityLevel); // if the entity is a viewer then update the viewer info if (theEntity.ViewerRecord!=NULL) { // determine the vision level (the bit of who_sees_me used by the viewer) TInvisibilityLevel visionLevel= (TInvisibilityLevel)(whoSeesMe>>NUM_WHOSEESME_BITS); // if the vision level has changed then we have stuff to do if (theEntity.ViewerRecord->getVisionLevel()!=visionLevel) { if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Changes vision level from %d to %d",dataSetRow.toString().c_str(),theEntity.ViewerRecord->getVisionLevel(),visionLevel); } // change the vision level for ourselves theEntity.ViewerRecord->setVisionLevel(visionLevel); // remove the entity from their current vision group theInstance->isolateViewer(theEntity); } } } } void CUniverse::registerVisionDeltaManager(IVisionDeltaManager* manager) { _VisionDeltaManager= manager; } void CUniverse::addVisionDelta(const CPlayerVisionDelta& visionDelta) { if (_VisionDeltaManager!=NULL) { _VisionDeltaManager->addVisionDelta(visionDelta); } } void CUniverse::update() { H_AUTO(CUniverse_Update) // deal with any delayed player positions who's timers have expired NLMISC::TGameCycle currentTick= CTickEventHandler::getGameCycle(); TDelayedPositions::iterator it= _DelayedPositions.begin(); TDelayedPositions::iterator itEnd= _DelayedPositions.end(); while(it!=itEnd) { // copy 'it' to 'curIt' and post incrment it TDelayedPositions::iterator curIt= it++; // if next entry in list's time is up then treat it if (curIt->GameCycle <= currentTick) { setEntityPosition(curIt->DataSetRow,curIt->X,curIt->Y); _DelayedPositions.erase(curIt); } } // deal with the updates for all of the instances for (uint32 i=0;i<_Instances.size();++i) { if (_Instances[i]!=NULL) { _Instances[i]->updateVision(); } } } inline void CUniverse::_addEntity(TDataSetRow dataSetRow,uint32 aiInstance,sint32 x,sint32 y,TInvisibilityLevel invisibilityLevel,bool isViewer) { uint32 row= dataSetRow.getIndex(); // increase _Entities vector size if required if (_Entities.size()<=row) { nlinfo("Increasing Universe::Entities vector size to: %d",row+1); _Entities.resize(row+1); } SUniverseEntity& theEntity= _Entities[row]; // if the entity was already allocated then remove it if (theEntity.AIInstance!=~0u) { _removeEntity(dataSetRow); } // if the entity is a viewer then setup their vision record if (isViewer) { theEntity.ViewerRecord= new CViewer; theEntity.ViewerRecord->init(dataSetRow,this); } // delegate to the teleport code to setup coordinates etc _teleportEntity(dataSetRow,aiInstance,x,y,invisibilityLevel); } inline void CUniverse::_removeEntity(TDataSetRow dataSetRow) { // get a handle to the entity SUniverseEntity& theEntity= _Entities[dataSetRow.getIndex()]; // If the entity was previously in an AIInstance then remove them if (theEntity.AIInstance<_Instances.size() && _Instances[theEntity.AIInstance]!=NULL) { // detach the entity from the instance it's currently in _Instances[theEntity.AIInstance]->removeEntity(theEntity); theEntity.AIInstance= ~0u; theEntity.ViewerRecord= NULL; } } inline void CUniverse::_setEntityPosition(TDataSetRow dataSetRow,sint32 x,sint32 y) { // get a handle to the entity SUniverseEntity& theEntity= _Entities[dataSetRow.getIndex()]; #ifdef NL_DEBUG nlassert(theEntity.AIInstance==~0u || theEntity.AIInstance<_Instances.size()); nlassert(theEntity.AIInstance==~0u || _Instances[theEntity.AIInstance]!=NULL); #endif // if the entity is a viewer then move the view coordinates if (theEntity.ViewerRecord!=NULL) { theEntity.ViewerRecord->setEntityPosition(x,y); } // if the entity is not currently in an AIInstance then stop here if (theEntity.AIInstance==~0u) return; // set the instance entity record position _Instances[theEntity.AIInstance]->setEntityPosition(theEntity.InstanceIndex,x,y); } inline void CUniverse::_teleportEntity(TDataSetRow dataSetRow,uint32 aiInstance,sint32 x,sint32 y,TInvisibilityLevel invisibilityLevel) { // get a handle to the entity SUniverseEntity& theEntity= _Entities[dataSetRow.getIndex()]; // If the entity was previously in a different AIInstance then remove them if (theEntity.AIInstance<_Instances.size() && _Instances[theEntity.AIInstance]!=NULL) { _Instances[theEntity.AIInstance]->removeEntity(theEntity); } // set the new AIInstance value for the entity theEntity.AIInstance= aiInstance; // if the aiInstance is set to ~0u (a reserved value) then we stop here if (aiInstance==~0u) { // clear out the vision for an entity in aiInstance ~0u if (getEntity(dataSetRow)->ViewerRecord!=NULL) { TVision emptyVision(2); nlctassert(sizeof(TDataSetRow)==sizeof(uint32)); emptyVision[0].DataSetRow= ZeroDataSetRow; emptyVision[1].DataSetRow=dataSetRow; getEntity(dataSetRow)->ViewerRecord->updateVision(emptyVision,this,false); } return; } // if the entity is a viewer then move the view coordinates if (theEntity.ViewerRecord!=NULL) { theEntity.ViewerRecord->setEntityPosition(x,y); } // tell the instance to receive the new entity _Instances[aiInstance]->addEntity(dataSetRow,x,y,invisibilityLevel,theEntity); } void CUniverse::dump(NLMISC::CLog& log) { log.displayNL("--------------------------------- Start of Ring Vision Universe"); log.displayNL("- Instance Vector Size: %u",_Instances.size()); log.displayNL("- Entities Vector Size: %u",_Entities.size()); log.displayNL("- Delayed Positions List Size: %u",_DelayedPositions.size()); for (uint32 i=0;i<_Instances.size();++i) { if (_Instances[i]!=NULL) { log.displayNL("--------------------------------- Start of Instance %u",i); _Instances[i]->dump(log); } } log.displayNL("--------------------------------- End of Ring Vision Universe"); } //----------------------------------------------------------------------------- // METHODS CInstance //----------------------------------------------------------------------------- CInstance::CInstance(CUniverse* theUniverse,uint32 groupId) { _TheUniverse= theUniverse; _GroupId=groupId; } uint32 CInstance::getGroupId() const { return _GroupId; } void CInstance::addEntity(TDataSetRow dataSetRow,uint32 x,uint32 y,TInvisibilityLevel invisibilityLevel,const SUniverseEntity& entity) { H_AUTO(CInstance_addEntity) // setup the field in the entity to say where it is in the instance's _Entities vector entity.InstanceIndex= _Entities.size(); // setup the new record for the entity (at the end of the _Entities vector) SInstanceEntity& theEntity= vectAppend(_Entities); theEntity.DataSetRow= dataSetRow; // call setEntityPosition() and setEntityInvisibilityLevel() to set the entity position and invisibility level setEntityPosition(entity.InstanceIndex,x,y); setEntityInvisibility(entity.InstanceIndex,invisibilityLevel); // if the entity is a viewer then add them to the viewers vector if (entity.ViewerRecord!=NULL) { // try to add add the entity into an existing vision group (must be within 50 meters of the group) bool foundVisionGroup= false; for (uint32 i=_VisionGroups.size();i--;) { // if the group is empty then skip it if (_VisionGroups[i]==NULL) continue; // if the group has a different vision level to us then skip it if (_VisionGroups[i]->getVisionLevel()!=entity.ViewerRecord->getVisionLevel()) continue; // determine the vision group size if the entity were a member uint32 dist= quickDist( x, y, (_VisionGroups[i]->xMin()+_VisionGroups[i]->xMax())>>1, (_VisionGroups[i]->yMin()+_VisionGroups[i]->yMax())>>1); // if the distance is ok then go ahead and add the entityt to the group if (distaddViewer(entity.ViewerRecord); foundVisionGroup=true; break; } } // no group was found so scan vector for an unused slot to use if (!foundVisionGroup) { for (uint32 i=_VisionGroups.size();i--;) { NLMISC::CSmartPtr& theVision= _VisionGroups[i]; if (theVision==NULL) { theVision= new CVisionGroup; theVision->setVisionId(i); } if (theVision->numViewers()==0) { foundVisionGroup= true; theVision->addViewer(entity.ViewerRecord); break; } } } // if still no group was found then create a new group for the entity if (!foundVisionGroup) { NLMISC::CSmartPtr& theVision= vectAppend(_VisionGroups); theVision= new CVisionGroup; theVision->setVisionId(_VisionGroups.size()-1); theVision->addViewer(entity.ViewerRecord); } } } inline void CInstance::setEntityPosition(uint32 entityIndex,uint32 x,uint32 y) { if(entityIndex<_Entities.size()) { _Entities[entityIndex].X=x; _Entities[entityIndex].Y=y; } else { BOMB(NLMISC::toString("BIG BAD BUG - Failed to move entity with invalid index: %d",entityIndex),return); } } inline void CInstance::setEntityInvisibility(uint32 entityIndex,TInvisibilityLevel invisibilityLevel) { if(entityIndex<_Entities.size()) { if ( VerboseVisionDelta && (_Entities[entityIndex].InvisibilityLevel != invisibilityLevel) ) { nlinfo("Vision Delta: %s: Changes invisibility level from %x to %x",_Entities[entityIndex].DataSetRow.toString().c_str(),_Entities[entityIndex].InvisibilityLevel,invisibilityLevel); } _Entities[entityIndex].InvisibilityLevel= invisibilityLevel; } else { BOMB(NLMISC::toString("BIG BAD BUG - Failed to set invisibilty level for entity with invalid index: %d",entityIndex),return); } } void CInstance::updateVision() { H_AUTO(CInstance_updateVision) // if we have no vision groups then don't waste our time if (_VisionGroups.empty()) return; // setup a local index to the used vision group objects static std::vector > visionGroups; visionGroups.clear(); // run throught the vision groups once updating the bounding coordinates and sort keys for (uint32 i=_VisionGroups.size();i--;) { NLMISC::CSmartPtr& theVisionGroup= _VisionGroups[i]; // skip empty groups if (theVisionGroup==NULL) continue; if (theVisionGroup->numViewers()==0) { theVisionGroup=NULL; continue; } // update bouding coordinates and sort keys theVisionGroup->updateBoundingCoords(); theVisionGroup->recalcSortKey(); // add the vision group to the local vision group list (sorting as we go) uint32 theSortKey= theVisionGroup->getSortKey(); uint32 j=visionGroups.size(); // point to back entry in vision group visionGroups.push_back(NULL); // create the new space that we're going to fill while (j!=0 && visionGroups[j-1]->getSortKey()>theSortKey) { uint32 oldj= j--; visionGroups[oldj]= visionGroups[j]; } visionGroups[j]= theVisionGroup; } // if no vision groups were found then skip out of instance update if (visionGroups.empty()) return; // run through the vision groups to update them for (uint32 i=visionGroups.size();i--;) { NLMISC::CSmartPtr& vi= visionGroups[i]; // if there are no viewers in the group (it was probably merged with another group) then skip if (vi->numViewers()==0) continue; // attempt to merge with groups that are nearby for (uint32 j=i;j--;) { NLMISC::CSmartPtr& vj= visionGroups[j]; // stop if the remaining groups are all too far away if (vi->getSortKey()-vj->getSortKey()>MaxVisionGroupRadius) break; if (visionGroupsOverlap(vi,vj)) { // merge the smaller vision object into the larger one if (vi->numViewers()>=vj->numViewers()) { vi->merge(vj); } else { vj->merge(vi); } // break out of the loop because one merge per group per cycle is enough break; } } // if we need to split the group then do it now... while ( quickDist(vi->xMin(),vi->yMin(),vi->xMax(),vi->yMax())> MinVisionGroupSplitDiameter || ( vi->numViewers()>MaxViewersPerGroup && quickDist(vi->xMin(),vi->yMin(),vi->xMax(),vi->yMax())> SmallestBucketSize ) ) { // find a free vision id to use uint32 visionSlot; for (visionSlot=0;visionSlot<_VisionGroups.size();++visionSlot) { if (_VisionGroups[visionSlot]==NULL || _VisionGroups[visionSlot]->numViewers()==0) break; } // if no free slot was found in the vision group vector then add a new group if (visionSlot==_VisionGroups.size()) _VisionGroups.push_back(NULL); // initialise the new group object NLMISC::CSmartPtr& theVision= _VisionGroups[visionSlot]; theVision= new CVisionGroup; theVision->setVisionId(visionSlot); // do the splitting vi->split(theVision); vi->updateBoundingCoords(); } // recalculate the group's vision vi->buildVision(_Entities); vi->updateViewers(_TheUniverse); } } void CInstance::isolateViewer(SUniverseEntity& entity) { H_AUTO(CInstance_isolateViewer) // if the entity is a viewer... CViewer* viewerRecord= &*entity.ViewerRecord; if (viewerRecord!=NULL) { // locate the old vision group (the one we're allocated to before isolation) uint32 visionId= viewerRecord->VisionId; NLMISC::CSmartPtr& oldVisionGroup= _VisionGroups[visionId]; BOMB_IF(visionId>=_VisionGroups.size() ||oldVisionGroup==NULL,"Trying to remove entity from vision group with unknown vision id",viewerRecord->VisionId=~0u;return); // if we're the only viewer then already isolated so just return if (oldVisionGroup->numViewers()==1) return; // remove from the old vision group oldVisionGroup->removeViewer(viewerRecord); // find a free vision id to use for the new vision group we're going to create uint32 visionSlot; for (visionSlot=0;visionSlot<_VisionGroups.size();++visionSlot) { if (_VisionGroups[visionSlot]==NULL || _VisionGroups[visionSlot]->numViewers()==0) break; } // if no free slot was found in the vision group vector then add a new group if (visionSlot==_VisionGroups.size()) _VisionGroups.push_back(NULL); // initialise the new group object NLMISC::CSmartPtr& newVisionGroup= _VisionGroups[visionSlot]; newVisionGroup= new CVisionGroup; newVisionGroup->setVisionId(visionSlot); // add the viewer into the new group newVisionGroup->addViewer(viewerRecord); if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Isolating viewer from vision group %d to new group %d",viewerRecord->getViewerId().toString().c_str(),oldVisionGroup->getVisionId(),visionSlot); } } } void CInstance::removeEntity(SUniverseEntity& entity) { H_AUTO(CInstance_removeEntity) // if the entity is a viewer... CViewer const * viewerRecord= &*entity.ViewerRecord; if (viewerRecord!=NULL) { uint32 visionId= viewerRecord->VisionId; BOMB_IF(visionId>=_VisionGroups.size() ||_VisionGroups[visionId]==NULL,"Trying to remove entity with unknown vision id",viewerRecord->VisionId=~0u;return); _VisionGroups[visionId]->removeViewer(viewerRecord); } // make sure the entity's InstanceIndex is valid uint32 entityIndex= entity.InstanceIndex; BOMB_IF(entityIndex>=_Entities.size(),"BIG BAD BUG - Entity's InstanceIndex is invalid!",return); // move the entity currently at the back of the vector to the slot that we're liberating (NOTE: this could be us!) SInstanceEntity& theEntitySlot= _Entities[entityIndex]; theEntitySlot=_Entities.back(); _TheUniverse->getEntity(theEntitySlot.DataSetRow)->InstanceIndex= entityIndex; // pop the back element off the entities vector _Entities.pop_back(); // invalidate the InstanceIndex value for the entity we just removed entity.InstanceIndex=~0u; entity.AIInstance=~0u; } void CInstance::release() { // iterate over vision groups getting them to clear their childrens' visions for (uint32 i=_VisionGroups.size();i--;) { if (_VisionGroups[i]!=NULL) { _VisionGroups[i]->release(_TheUniverse); } } // remove all of the entities from the instance for (uint32 i=_Entities.size();i--;) { SUniverseEntity* theEntity= _TheUniverse->getEntity(_Entities[i].DataSetRow); BOMB_IF(theEntity==NULL,"Failed to locate universe entity corresponding to instance entity in CInstance::realease()",continue); removeEntity(*theEntity); } } void CInstance::dump(NLMISC::CLog& log) { // display a few stats log.displayNL("- GroupId: %u",_GroupId); log.displayNL("- Entities Vector Size: %u",_Entities.size()); log.displayNL("- Vision Groups Vector Size: %u",_VisionGroups.size()); // display all viewable entities in the instance for (uint32 i=0;i<_Entities.size();++i) { log.displayNL("- Entity %u: %s (%d,%d) InvisibilityLevel(%x)",i,_Entities[i].DataSetRow.toString().c_str(),_Entities[i].X,_Entities[i].Y,_Entities[i].InvisibilityLevel); } // display all vision groups for (uint32 i=0;i<_VisionGroups.size();++i) { log.display("- Group %u: ",i); _VisionGroups[i]->dump(log); } } //----------------------------------------------------------------------------- // METHODS CVisionGroup //----------------------------------------------------------------------------- CVisionGroup::CVisionGroup() { _XMin=~0u; _XMax=0; _YMin=~0u; _YMax=0; _VisionId=~0u; // setup the dummy entry in the vision buffer _Vision.reserve(AllocatedVisionVectorSize); vectAppend(_Vision).DataSetRow= ZeroDataSetRow; } void CVisionGroup::release(CUniverse* theUniverse) { // clear out vision (leaving only the reserved entry in slot 0) _Vision.resize(1); // iterate over the viewers to have them update their visions updateViewers(theUniverse); } void CVisionGroup::addViewer(CViewer* viewer) { // ensure that the viewer wasn't aleady attached to another vision group #ifdef NL_DEBUG nlassert(viewer!=NULL); nlassert(viewer->VisionId==~0u); nlassert(viewer->VisionIndex==~0u); #endif TEST(("add viewer %d to grp %d",viewer->getViewerId().getIndex(),_VisionId)); // flag the viewer as belongning to this vision, with this vision index viewer->VisionId=_VisionId; viewer->VisionIndex=_Viewers.size(); // add the new viewer to the viewers vector _Viewers.push_back(viewer); // update the boudary coordinates of the vision group _XMin= std::min(_XMin,viewer->getX()); _YMin= std::min(_YMin,viewer->getY()); _XMax= std::max(_XMax,viewer->getX()); _YMax= std::max(_YMax,viewer->getY()); } void CVisionGroup::removeViewer(const CViewer* viewer) { // ensure that the viewer was aleady attached to this vision group #ifdef NL_DEBUG nlassert(viewer!=NULL); nlassert(viewer->VisionId==_VisionId); nlassert(viewer->VisionIndex<_Viewers.size()); nlassert(_Viewers[viewer->VisionIndex]==viewer); #endif TEST(("remove viewer %d from grp %d",viewer->getViewerId().getIndex(),_VisionId)); // move the back entry in the viewers buffer into our slot (NOTE: the back entry can be us!) uint32 visionIndex= viewer->VisionIndex; _Viewers[visionIndex]= _Viewers.back(); _Viewers[visionIndex]->VisionIndex= visionIndex; // pop the back entry off the vector and flag oursleves as unused _Viewers.pop_back(); viewer->VisionId= ~0u; viewer->VisionIndex= ~0u; // NOTE: after this the boundary may be out of date - this will be recalculated at the next // vision update so we don't take time to do it here } void CVisionGroup::merge(CVisionGroup* other) { H_AUTO(CVisionGroup_merge) #ifdef NL_DEBUG nlassert(other!=NULL); #endif TEST(("merge %d with %d",_VisionId,other->_VisionId)); // merge the viewer vectors for (uint32 i=other->_Viewers.size();i--;) { // get a pointer to the viewer object that we're moving from the other vision group NLMISC::CSmartPtr& theViewer= other->_Viewers[i]; // update the vision id to point to ourselves and the next slot in our viewers vector theViewer->VisionId=_VisionId; theViewer->VisionIndex=_Viewers.size(); TEST(("moveGroup ent=%d old grp=%d new grp=%d",theViewer->getViewerId().getIndex(),other->_VisionId,_VisionId)); // push the element onto our viewers vector _Viewers.push_back(theViewer); } // merge the viewer bounding coordinates _XMin= std::min(_XMin,other->_XMin); _YMin= std::min(_YMin,other->_YMin); _XMax= std::max(_XMax,other->_XMax); _YMax= std::max(_YMax,other->_YMax); // empty out the merged vision group other->_Viewers.clear(); other->_Vision.resize(1); // keep the dummy entry intact at the base of the vision vector other->_XMin= other->_XMax= other->_YMin= other->_YMax= 0; } void CVisionGroup::split(CVisionGroup* other) { H_AUTO(CVisionGroup_split) #ifdef NL_DEBUG nlassert(other!=NULL); #endif TEST(("split grp %d into grp %d",_VisionId,other->_VisionId)); // make sure we don't try splitting an empty vision group BOMB_IF(_Viewers.empty(),"Attempting to split a vision group with no members",return); // setup some handy locals uint32 viewerSize= _Viewers.size(); // setup a couple of vectors to hold x coordinates and y coordinates static std::vector xvect; static std::vector yvect; xvect.resize(viewerSize); yvect.resize(viewerSize); // run through the viewers, filling in the x and y vectors for (uint32 i=viewerSize;i--;) { const NLMISC::CSmartPtr& theViewer= _Viewers[i]; xvect[i]=theViewer->getX(); yvect[i]=theViewer->getY(); } // sort the 2 vectors std::sort(xvect.begin(),xvect.end()); std::sort(yvect.begin(),yvect.end()); // locate the biggest gap along each axis uint32 longestGap=0; bool longestIsX=true; uint32 cutPos=0; // setup a bias to favour splitting in the middle of large evenly spread groups sint32 biasDelta=1000; // a bias of 1 meter per individual who we split off sint32 bias= sint32(-biasDelta*viewerSize)>>1; sint32 maxBias= -bias; // iterate down from entry (viewerSize-1)..1 (skip 0) for (uint32 i=viewerSize;--i;) { // update the bias score bias+=biasDelta; sint32 absBias= maxBias-abs(bias); // calculate distances from entry i-1 to entry i in each of x and y vectors uint32 xdist= xvect[i]-xvect[i-1]+absBias; uint32 ydist= yvect[i]-yvect[i-1]+absBias; // if our xdist is a new record then take note, use it as the new refference if (xdist > longestGap) { longestGap=xdist; longestIsX=true; cutPos=i; } // if our ydist is a new record then take note, use it as the new refference if (ydist > longestGap) { longestGap=ydist; longestIsX=false; cutPos=i; } } // migrate entries over to the new vector uint32 splitCount= viewerSize-cutPos; other->_Viewers.resize(splitCount); if (longestIsX) { uint32 leftIdx=0, rightIdx=viewerSize-1, otherIdx=0, splitVal= xvect[cutPos]; while (otherIdx!=splitCount) { // move elements from the right end of this vector to the other vector while (_Viewers[rightIdx]->getX()>= splitVal) { TEST(("moveGroup ent=%d old grp=%d new grp=%d",_Viewers[rightIdx]->getViewerId().getIndex(),_VisionId,other->_VisionId)); other->_Viewers[otherIdx]= _Viewers[rightIdx]; // update the vision index info other->_Viewers[otherIdx]->VisionIndex=otherIdx; other->_Viewers[otherIdx]->VisionId=other->_VisionId; // update the iterators otherIdx++; rightIdx--; } // deal with elements off the right end of this vector that need to be re-housed while (_Viewers[rightIdx]->getX()< splitVal && otherIdx!=splitCount) { // skip elements at the left end of this vector that are to be left untouched while (_Viewers[leftIdx]->getX()< splitVal) { ++leftIdx; } TEST(("moveGroup ent=%d old grp=%d new grp=%d",_Viewers[leftIdx]->getViewerId().getIndex(),_VisionId,other->_VisionId)); // move the element reffered to by leftIdx to the other vector and replace it from the rightIdx other->_Viewers[otherIdx]= _Viewers[leftIdx]; _Viewers[leftIdx]= _Viewers[rightIdx]; // update the vision index info other->_Viewers[otherIdx]->VisionIndex=otherIdx; other->_Viewers[otherIdx]->VisionId=other->_VisionId; _Viewers[leftIdx]->VisionIndex=leftIdx; // update the iterators otherIdx++; leftIdx++; rightIdx--; } } _Viewers.resize(cutPos); } else { uint32 leftIdx=0, rightIdx=viewerSize-1, otherIdx=0, splitVal= yvect[cutPos]; while (otherIdx!=splitCount) { // move elements from the right end of this vector to the other vector while (_Viewers[rightIdx]->getY()>= splitVal) { other->_Viewers[otherIdx]= _Viewers[rightIdx]; // update the vision index info other->_Viewers[otherIdx]->VisionIndex=otherIdx; other->_Viewers[otherIdx]->VisionId=other->_VisionId; // update the iterators otherIdx++; rightIdx--; } // deal with elements off the right end of this vector that need to be re-housed while (_Viewers[rightIdx]->getY()< splitVal && otherIdx!=splitCount) { // skip elements at the left end of this vector that are to be left untouched while (_Viewers[leftIdx]->getY()< splitVal) { ++leftIdx; } // move the element reffered to by leftIdx to the other vector and replace it from the rightIdx other->_Viewers[otherIdx]= _Viewers[leftIdx]; _Viewers[leftIdx]= _Viewers[rightIdx]; // update the vision index info other->_Viewers[otherIdx]->VisionIndex=otherIdx; other->_Viewers[otherIdx]->VisionId=other->_VisionId; _Viewers[leftIdx]->VisionIndex=leftIdx; // update the iterators otherIdx++; leftIdx++; rightIdx--; } } _Viewers.resize(cutPos); } } void CVisionGroup::updateBoundingCoords() { // if there are no viewers then stop here and don't waste any more time if (_Viewers.empty()) return; // calculate the bouding box for our viewers uint32 xmin= ~0u; uint32 ymin= ~0u; uint32 xmax= 0; uint32 ymax= 0; for (uint32 i=_Viewers.size();i--;) { const CViewer& viewer=*_Viewers[i]; xmin= std::min(xmin,viewer.getX()); ymin= std::min(ymin,viewer.getY()); xmax= std::max(xmax,viewer.getX()); ymax= std::max(ymax,viewer.getY()); } _XMin= xmin; _YMin= ymin; _XMax= xmax; _YMax= ymax; } void CVisionGroup::buildVision(const TInstanceEntities& entities) { H_AUTO(CVisionGroup_buildVision) // if there are no viewers then stop here and don't waste any more time if (_Viewers.empty()) return; // generate the ref coordinates for the centre of our vision group uint32 xref= _XMin/2+_XMax/2; // note - we do divide before add to avoid math errors !!! uint32 yref= _YMin/2+_YMax/2; // note - we do divide before add to avoid math errors !!! // build a little set of buckets to sort our entities into // these are static purely for reasons of optimisation static std::vector buckets(MaxVisionEntries*NumVisionBuckets); uint32 bucketIndex[NumVisionBuckets]; for (uint32 i=0;ivisionLevel) { continue; } // calculate the distance value for this entity uint32 dist= quickDist(theEntity.X,theEntity.Y,xref,yref); // if this entity is in range then add a pointer to it to our index if (dist<=MaxVisionDist) { // get a bucket index for the entity uint32 bucket= dist>>VisionBucketShift; // get a refference to the appropriate bucket index uint32& theBucketIndex= bucketIndex[bucket]; if (theBucketIndex>=buckets.size()) continue; buckets[theBucketIndex]= &theEntity; theBucketIndex+= NumVisionBuckets; } } // reset the vision vector and add in the dummy entry with DataSetRow=0 _Vision.resize(AllocatedVisionVectorSize); _Vision[0].DataSetRow= ZeroDataSetRow; _Vision[0].VisionSlot= ~0u; // setup a vision slot iterator for filling in the vision buffer (=1 to skip passed the dummy entry) uint32 nextVisionSlot=1; // run through the buckets adding their contents to the vision vector for (uint32 i=0;iMaxVisionEntries) { BOMB_IF(i==0,"ERROR: Vision calculated with >250 entities in first vision bucket!",break); break; } // add the contents of this bucket to the vision vector while(numBucketEntries--) { theBucketIndex-= NumVisionBuckets; _Vision[nextVisionSlot++].DataSetRow= buckets[theBucketIndex]->DataSetRow; } } // resize the vision buffer down to fit the size really used _Vision.resize(nextVisionSlot); // sort the vision buffer std::sort(_Vision.begin(),_Vision.end()); } void CVisionGroup::updateViewers(CUniverse* theUniverse) { // iterate over the viewers, udating their vision for (uint32 i=_Viewers.size();i--;) { _Viewers[i]->updateVision(_Vision,theUniverse,false); } } void CVisionGroup::dump(NLMISC::CLog& log) { // display a few stats log.displayNL("VisionId(%u) Min(%d,%d)..Max(%d,%d) sortKey(%u)",_VisionId,_XMin,_YMin,_XMax,_YMax,_SortKey); log.displayNL("-- Viewers Vector Size: %u",_Viewers.size()); log.displayNL("-- Vision Vector Size: %u",_Vision.size()); // the vector of viewers for (uint32 i=0;i<_Viewers.size();++i) { if (_Viewers[i]==NULL) continue; log.display("-- Viewer %u: ",i); _Viewers[i]->dump(log); } // the group's vision std::string visionString; for (uint32 i=0;i<_Vision.size();++i) { if (!visionString.empty()) visionString+= ' '; visionString+= _Vision[i].DataSetRow.toString(); } log.displayNL("-- Vision: %s",visionString.c_str()); } inline uint32 CVisionGroup::xMin() const { return _XMin; } inline uint32 CVisionGroup::xMax() const { return _XMax; } inline uint32 CVisionGroup::yMin() const { return _YMin; } inline uint32 CVisionGroup::yMax() const { return _YMax; } inline uint32 CVisionGroup::getSortKey() const { return _SortKey; } inline void CVisionGroup::setSortKey(uint32 sortKey) { _SortKey= sortKey; } inline void CVisionGroup::recalcSortKey() { _SortKey= quickDist(0,0,(_XMin+_XMax)/2,(_YMin+_YMax)/2); } inline uint32 CVisionGroup::numViewers() const { return _Viewers.size(); } inline void CVisionGroup::setVisionId(uint32 visionId) { _VisionId= visionId; } inline uint32 CVisionGroup::getVisionId() const { return _VisionId; } inline TInvisibilityLevel CVisionGroup::getVisionLevel() const { if (_Viewers.empty()) return VISIBLE; return (**_Viewers.begin()).getVisionLevel(); } //----------------------------------------------------------------------------- // METHODS CViewer //----------------------------------------------------------------------------- CViewer::CViewer() { VisionId=~0u; VisionIndex=~0u; _VisionResetCount= 0; } void CViewer::init(TDataSetRow viewerId,CUniverse* theUniverse) { // ensure that we don't try to init the same object more than once BOMB_IF(_ViewerId!=TDataSetRow(),"Call to init() for already initialised CViewer object",return); // setup the viewer id required for filling in vision update messages _ViewerId= viewerId; // setup the viewed entities vector with reserved slot 0 _Vision.reserve(AllocatedVisionVectorSize); _Vision.resize(1); // setup the dummy entry with DataSetRow=0 _Vision[0].DataSetRow= ZeroDataSetRow; _Vision[0].VisionSlot= ~0u; // setup the vision slots in reverse order from 254..0 (because they're popped from the back) _FreeVisionSlots.clear(); _FreeVisionSlots.resize(AllocatedVisionVectorSize-1); for (uint32 i=0;i0; i--) { // get a refference to the vision entry to be dealt with (removed) SViewedEntity& visionEntry= _Vision[i]; // special case - never delete self from vision if (visionEntry.VisionSlot==0) { playerVisionEntryIndex= i; continue; } // delete a vision entry that no longer exists // add to the 'removed vision entries' result vector result.EntitiesOut.push_back(CPlayerVisionDelta::CIdSlot(visionEntry.DataSetRow,(uint8)visionEntry.VisionSlot)); TEST(("Vision DROP entity %d[%d] x %d for vision reset",_ViewerId.getIndex(),visionEntry.VisionSlot,visionEntry.DataSetRow.getIndex())); // if we're in verbose log mode then do a bit of logging if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Generating empty vision because _VisionResetCounter!=0",_ViewerId.toString().c_str()); } // add to the vector of free vision slots _FreeVisionSlots.push_back(visionEntry.VisionSlot); } // only keep 2 slots: slot #1: self , #0: the front-of-vision marker _Vision[1]= _Vision[playerVisionEntryIndex]; _Vision.resize(2); } void CViewer::_updateVisionNormal(const TVision& vision,CUniverse* theUniverse,bool firstTime,CPlayerVisionDelta& result) { // prepare to apply our group's vision to our own... sint32 oldVisionIdx= _Vision.size()-1; // note that there is always a dummy entry at start of vision vector sint32 newVisionIdx= vision.size()-1; // note that there is always a dummy entry at start of vision vector static std::vector newVisionEntries; // temp vector - static for reasons of optimisation newVisionEntries.clear(); // run through the two sorted vision vectors from top to bottom, stopping when they both reach the bottom while ((newVisionIdx|oldVisionIdx)!=0) { const SViewedEntity& newVisionEntry= vision[newVisionIdx]; SViewedEntity& oldVisionEntry= _Vision[oldVisionIdx]; // see whether we have a new entry to add, an unchanged entry to ignore or an out of date entry to delete if (newVisionEntry.DataSetRow==oldVisionEntry.DataSetRow) { // we have an entry that hasn't changed (its in both old and new vision) newVisionEntry.VisionSlot= oldVisionEntry.VisionSlot; --newVisionIdx; --oldVisionIdx; } else { if (newVisionEntry.DataSetRow.getIndex()>oldVisionEntry.DataSetRow.getIndex()) { // make sure we don't add ourselves back into our own vision a second time after teleportation etc // but do allow us to be added in on the first pass when _Vision is empty (there is always a vision // terminator entry so the empty vision case comes when _Vision.size()==1) if (newVisionEntry.DataSetRow!=_ViewerId || firstTime) { // add a new entry to the vision // add to a temp vector of new entries that haven't yet had a vision slot allocation newVisionEntries.push_back(newVisionIdx); } else { // force ourselves into vision slot 0 (where we belong) newVisionEntry.VisionSlot= 0; if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Becomes visible to self",_ViewerId.toString().c_str()); } } --newVisionIdx; } else { // make sure we never delete ourselves from our own vision... uint32 visionSlot= oldVisionEntry.VisionSlot; if (visionSlot!=0) { // delete a vision entry that no longer exists // add to the 'removed vision entries' result vector result.EntitiesOut.push_back(CPlayerVisionDelta::CIdSlot(oldVisionEntry.DataSetRow,(uint8)visionSlot)); TEST(("Vision DROP entity %d[%d] x %d (grp %d)",_ViewerId.getIndex(),visionSlot,oldVisionEntry.DataSetRow.getIndex(),VisionId)); // add to the vector of free vision slots _FreeVisionSlots.push_back(visionSlot); } else { if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Rescuing Vision entry when going invisible",_ViewerId.toString().c_str()); } } --oldVisionIdx; } } } // calculate the number of free vision slots remaining once we deal with the new vision entries sint32 freeVisionSlotOffset= (sint32)(_FreeVisionSlots.size()-newVisionEntries.size()); BOMB_IF(freeVisionSlotOffset<0,"BIG BAD BUG generating vision ... too few free vision slots found",newVisionEntries.resize(_FreeVisionSlots.size())); // deal with new vision entries for (uint32 i= newVisionEntries.size();i--;) { // derefference the new vision entry const SViewedEntity& visionEntry= vision[newVisionEntries[i]]; // lookup the next free vision slot visionEntry.VisionSlot= _FreeVisionSlots[freeVisionSlotOffset+i]; // add the new entry to the result vector result.EntitiesIn.push_back(CPlayerVisionDelta::CIdSlot(visionEntry.DataSetRow,(uint8)visionEntry.VisionSlot)); TEST(("Vision ADD entity %d[%d] = %d (grp %d)",_ViewerId.getIndex(),visionEntry.VisionSlot,visionEntry.DataSetRow.getIndex(),VisionId)); } // resize down the free slots vector to deal with the slots we just allocated _FreeVisionSlots.resize(freeVisionSlotOffset); // update our vision vector _Vision= vision; } void CViewer::updateVision(const TVision& vision,CUniverse* theUniverse,bool firstTime) { H_AUTO(CViewer_UpdateVision) // we conserve a static variable that we use for building our results in as it saves time static CPlayerVisionDelta result; result.reset(_ViewerId); // make sure we've been properly initialised BOMB_IF(_ViewerId==TDataSetRow(),"Call to updateVision() for uninitialised CViewer object",return); // if we're resting the player vision then treat ourselves as blind unless this is the first update in which case we need to // be allowed to add ourselves into our own vision if (_VisionResetCount!=0 && !firstTime) { // if we're in verbose log mode then do a bit of logging if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: Generating empty vision because _VisionResetCounter!=0",_ViewerId.toString().c_str()); } // decrement the count of vision iterations still to be skipped to complete this reset --_VisionResetCount; // perform a vision update that clears out all vision contents other than self _updateVisionBlind(theUniverse,result); } else { // perform a normal vision update _updateVisionNormal(vision,theUniverse,firstTime,result); } // if the vision delta isn't empty then dispatch it if (!result.empty()) { // if we're in verbose log mode then do a bit of logging if (VerboseVisionDelta) { nlinfo("Vision Delta: %s: %s",_ViewerId.toString().c_str(),result.toString().c_str()); } // add our result to the messages to be dispatched to the FES at end of tick... theUniverse->addVisionDelta(result); } } void CViewer::dump(NLMISC::CLog& log) { // display a few stats log.displayNL("%s (%d,%d) VisionLvl(%u)",_ViewerId.toString().c_str(),_X,_Y,_VisionLevel); // the entity's vision std::string visionString; for (uint32 i=0;i<_Vision.size();++i) { if (!visionString.empty()) visionString+= ' '; visionString+= NLMISC::toString("[%u]",_Vision[i].VisionSlot)+ _Vision[i].DataSetRow.toString(); } log.displayNL("--- Vision: %s",visionString.c_str()); // the entity's free vision slots std::string freeVisionSlotsString; for (uint32 i=_FreeVisionSlots.size();i--;) { if (!freeVisionSlotsString.empty()) freeVisionSlotsString+= ' '; freeVisionSlotsString+= NLMISC::toString("%u",_FreeVisionSlots[i]); } log.displayNL("--- Free Vision Slots: %s",visionString.c_str()); } inline TDataSetRow CViewer::getViewerId() const { return _ViewerId; } inline uint32 CViewer::getViewerIdx() const { return _ViewerId.getIndex(); } inline void CViewer::setEntityPosition(uint32 x, uint32 y) { _X=x; _Y=y; } inline uint32 CViewer::getX() const { return _X; } inline uint32 CViewer::getY() const { return _Y; } inline void CViewer::forceResetVision() { // if we're not already resetting vision... if (_VisionResetCount == 0) { // setup the vision counter to reset vision for next n updates in order to allow // FES time to clear out player vision _VisionResetCount = VisionResetDuration; } } inline void CViewer::setVisionLevel(TInvisibilityLevel visionLevel) { _VisionLevel= visionLevel; } inline TInvisibilityLevel CViewer::getVisionLevel() const { return _VisionLevel; } } // namespace R2_VISION