// 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