khanat-opennel-code/code/ryzom/client/src/entities.cpp

2249 lines
64 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// 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 <http://www.gnu.org/licenses/>.
/////////////
// INCLUDE //
/////////////
#include "stdpch.h"
// Client
#include "entities.h"
#include "entity_cl.h"
#include "fx_cl.h"
#include "forage_source_cl.h"
#include "item_cl.h"
#include "pacs_client.h"
#include "time_client.h"
#include "view.h"
#include "user_entity.h"
#include "sheet_manager.h"
#include "motion/user_controls.h"
#include "net_manager.h"
#include "debug_client.h"
#include "ingame_database_manager.h"
#include "interface_v3/interface_manager.h"
#include "door_manager.h"
#include "projectile_manager.h"
#include "client_chat_manager.h"
#include "interface_v3/people_interraction.h"
#include "interface_v3/bar_manager.h"
#include "interface_v3/group_compas.h"
// 3D
#include "nel/3d/quad_tree.h"
// Interface 3D
#include "nel/3d/u_driver.h"
#include "nel/3d/u_scene.h"
#include "nel/3d/u_camera.h"
#include "nel/3d/u_text_context.h"
#include "nel/3d/u_material.h"
// Misc
#include "nel/misc/stream.h"
#include "nel/misc/common.h"
// Game share
#include "game_share/mission_desc.h"
#include "game_share/inventories.h"
// PACS
#include "nel/pacs/u_collision_desc.h"
// UI
#include "interface_v3/group_compas.h"
#include "player_r2_cl.h"
#include "r2/editor.h"
///////////
// USING //
///////////
using namespace NLMISC;
using namespace NL3D;
using namespace std;
////////////
// EXTERN //
////////////
extern UDriver *Driver;
extern UScene *Scene;
extern UTextContext *TextContext;
extern UCamera MainCam;
extern CLFECOMMON::TCLEntityId SlotUnderCursor;
////////////
// GLOBAL //
////////////
CEntityManager EntitiesMngr;
// Hierarchical timer
H_AUTO_DECL ( RZ_Client_Entity_Mngr_Update )
H_AUTO_DECL ( RZ_Client_Update_Post_Render )
H_AUTO_DECL ( RZ_Client_Entity_Mngr_Update_Apply_Motion )
H_AUTO_DECL ( RZ_Client_Entity_Mngr_Update_Count )
/////////////
// METHODS //
/////////////
//---------//
// PRIVATE //
//---------//
// ***************************************************************************
class CMissionTargetObserver : public ICDBNode::IPropertyObserver
{
public :
// From ICDBNode::IPropertyObserver
virtual void update(ICDBNode* node )
{
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
// Get the target
uint32 oldTarget = leaf->getOldValue32();
uint32 target = leaf->getValue32();
// Scan all entities
CEntityCL *entity = NULL;
if (oldTarget)
entity = EntitiesMngr.getEntityByName(oldTarget);
if (entity)
entity->updateMissionTarget();
entity = NULL;
if (target)
entity = EntitiesMngr.getEntityByName(target);
if (entity)
entity->updateMissionTarget();
CInterfaceManager *im = CInterfaceManager::getInstance();
CGroupCompas *gc = dynamic_cast<CGroupCompas *>(im->getElementFromId("ui:interface:compass"));
// if new target title is not NULL, then show the compass and make it blink to indicate new location
// please note that the first time the player login, a target has not been saved in his config file, so
// we permit the first (and only one) mission that is received to become the new compass direction (chiang the strong ...)
if (!IngameDbMngr.initInProgress() || (gc && !gc->isSavedTargetValid()))
{
if (target)
{
_PendingMissionTitle.push_back(leaf);
}
}
}
}
// When a mission name has been retrieved, update the compass to point it
void update()
{
std::list<CCDBNodeLeaf *>::iterator it = _PendingMissionTitle.begin();
while (it != _PendingMissionTitle.end())
{
std::list<CCDBNodeLeaf *>::iterator tmpIt = it;
++ it;
CCDBNodeLeaf *leaf = *tmpIt;
// If the previous title is not empty we probably have to clear the compass
if (leaf->getOldValue32() != 0)
{
CInterfaceManager *pIM = CInterfaceManager::getInstance();
CGroupCompas *pGC = dynamic_cast<CGroupCompas*>(pIM->getElementFromId("ui:interface:compass"));
if (pGC == NULL)
{
nlwarning("Can't retrieve compass group");
return;
}
CCompassTarget ct = pGC->getTarget();
STRING_MANAGER::CStringManagerClient *pSMC = STRING_MANAGER::CStringManagerClient::instance();
ucstring oldName;
if (!pSMC->getDynString(leaf->getOldValue32(), oldName))
{
nlwarning("Can't get compass target name");
return;
}
if (ct.Name == oldName)
{
CCompassTarget north;
pGC->setTarget(north);
}
}
// see if mission name has been retrieved
if ((*tmpIt)->getValue32() == 0)
{
_PendingMissionTitle.erase(tmpIt);
}
else
{
// TODO : maybe the following code could be include in CGroupMap::checkCoords, but it is not called when the map is not visible...
STRING_MANAGER::CStringManagerClient *pSMC = STRING_MANAGER::CStringManagerClient::instance();
ucstring name;
if (pSMC->getDynString((*tmpIt)->getValue32(), name))
{
// if (_AlreadyReceived.count(name) == 0)
// {
// _AlreadyReceived.insert(name);
CInterfaceManager *im = CInterfaceManager::getInstance();
CGroupCompas *gc = dynamic_cast<CGroupCompas *>(im->getElementFromId("ui:interface:compass"));
if (!gc)
{
nlwarning("Can't retrieve compass group");
}
else
{
CCompassTarget ct;
CCDBNodeLeaf *leaf = *tmpIt;
CCDBNodeBranch *parent = leaf->getParent();
if (parent)
{
CCDBNodeLeaf *x = dynamic_cast<CCDBNodeLeaf *>(parent->getNode(ICDBNode::CTextId("X")));
CCDBNodeLeaf *y = dynamic_cast<CCDBNodeLeaf *>(parent->getNode(ICDBNode::CTextId("Y")));
if (x && y)
{
CSmartPtr<CNamedEntityPositionState> tracker = new CNamedEntityPositionState;
tracker->build(*tmpIt, x, y);
ct.setPositionState(tracker);
ct.Name = name;
// make the compass appear and blink
gc->setActive(true);
gc->setTarget(ct);
gc->blink();
gc->enableBlink(2);
im->setTopWindow(gc);
}
}
}
// }
_PendingMissionTitle.erase(tmpIt);
}
}
}
}
private:
std::list<CCDBNodeLeaf *> _PendingMissionTitle;
// std::set<ucstring> _AlreadyReceived;
};
//-----------------------------------------------
CMissionTargetObserver MissionTargetObserver;
// ***************************************************************************
class CTeamUIDObserver : public ICDBNode::IPropertyObserver
{
public :
// From ICDBNode::IPropertyObserver
virtual void update(ICDBNode* node )
{
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
// Get the uid
CLFECOMMON::TClientDataSetIndex oldEntityId = leaf->getOldValue32();
CLFECOMMON::TClientDataSetIndex entityId = leaf->getValue32();
// Scan all entities.
// check if removed from team
CEntityCL *entity = EntitiesMngr.getEntityByCompressedIndex(oldEntityId);
if (entity)
entity->updateIsInTeam();
// check if added in team
entity = EntitiesMngr.getEntityByCompressedIndex(entityId);
if (entity)
entity->updateIsInTeam();
}
}
};
//-----------------------------------------------
CTeamUIDObserver TeamUIDObserver;
// ***************************************************************************
class CTeamPresentObserver : public ICDBNode::IPropertyObserver
{
public :
// From ICDBNode::IPropertyObserver
virtual void update(ICDBNode* node )
{
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
// Must get the NAME leaf
CCDBNodeBranch *parent= leaf->getParent();
if(parent)
{
leaf= dynamic_cast<CCDBNodeLeaf*>(parent->getNode(ICDBNode::CTextId("UID"), false));
// Get the name id
CLFECOMMON::TClientDataSetIndex entityId = CLFECOMMON::INVALID_CLIENT_DATASET_INDEX;
if(leaf)
entityId= leaf->getValue32();
// Scan all entities.
// check if added/removed in team
CEntityCL *entity= EntitiesMngr.getEntityByCompressedIndex(entityId);
if (entity)
entity->updateIsInTeam();
}
}
}
};
//-----------------------------------------------
CTeamPresentObserver TeamPresentObserver;
// ***************************************************************************
class CAnimalUIDObserver : public ICDBNode::IPropertyObserver
{
public :
// From ICDBNode::IPropertyObserver
virtual void update(ICDBNode* node )
{
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
// Get the uid
CLFECOMMON::TClientDataSetIndex oldEntityId = leaf->getOldValue32();
CLFECOMMON::TClientDataSetIndex entityId = leaf->getValue32();
// Scan all entities.
// check if removed from animal list
CEntityCL *entity = EntitiesMngr.getEntityByCompressedIndex(oldEntityId);
if (entity)
entity->updateIsUserAnimal();
// check if added in animal list
entity = EntitiesMngr.getEntityByCompressedIndex(entityId);
if (entity)
entity->updateIsUserAnimal();
}
}
};
//-----------------------------------------------
CAnimalUIDObserver AnimalUIDObserver;
// ***************************************************************************
class CAnimalStatusObserver : public ICDBNode::IPropertyObserver
{
public :
// From ICDBNode::IPropertyObserver
virtual void update(ICDBNode* node )
{
CCDBNodeLeaf *leaf = dynamic_cast<CCDBNodeLeaf*>(node);
if (leaf)
{
// Must get the NAME leaf
CCDBNodeBranch *parent= leaf->getParent();
if(parent)
{
leaf= dynamic_cast<CCDBNodeLeaf*>(parent->getNode(ICDBNode::CTextId("UID"), false));
// Get the name id
CLFECOMMON::TClientDataSetIndex entityId = CLFECOMMON::INVALID_CLIENT_DATASET_INDEX;
if(leaf)
entityId= leaf->getValue32();
// Scan all entities.
// check if added/removed in animal list
CEntityCL *entity= EntitiesMngr.getEntityByCompressedIndex(entityId);
if (entity)
entity->updateIsUserAnimal();
}
}
}
};
//-----------------------------------------------
CAnimalStatusObserver AnimalStatusObserver;
// ***************************************************************************
//--------//
// PUBLIC //
//--------//
//-----------------------------------------------
// CEntityManager :
// Constructor.
//-----------------------------------------------
CEntityManager::CEntityManager()
{
_NbMaxEntity = 0;
_EntitiesAllocated = 0;
_NbUser = 0;
_NbPlayer = 0;
_NbChar = 0;
_LastEntityUnderPos= NULL;
}// CEntityManager //
//-----------------------------------------------
// ~CEntityManager :
// Destructor.
//-----------------------------------------------
CEntityManager::~CEntityManager()
{
release();
}// ~CEntityManager //
//-----------------------------------------------
// initialize :
//
//-----------------------------------------------
void CEntityManager::initialize(uint nbMaxEntity)
{
// Set the maximum number of entities.
_NbMaxEntity = nbMaxEntity;
// if
if(_NbMaxEntity)
{
_Entities.resize(_NbMaxEntity, 0);
_EntityGroundFXHandle.resize(_NbMaxEntity);
}
// Add an observer on the mission database
CInterfaceManager *pIM = CInterfaceManager::getInstance();
uint i,j;
for (i=0; i<MAX_NUM_MISSIONS; i++)
for (j=0; j<MAX_NUM_MISSION_TARGETS; j++)
{
pIM->addDBObserver(&MissionTargetObserver, "SERVER:MISSIONS:"+toString(i)+":TARGET"+toString(j)+":TITLE");
}
// Add an Observer to the Team database
for (i=0; i<MaxNumPeopleInTeam; i++)
{
pIM->addDBObserver(&TeamUIDObserver, toString(TEAM_DB_PATH ":%d:UID", i));
pIM->addDBObserver(&TeamPresentObserver, toString(TEAM_DB_PATH ":%d:NAME", i));
}
// Add an Observer to the Animal database
for (i=0; i<MAX_INVENTORY_ANIMAL; i++)
{
pIM->addDBObserver(&AnimalUIDObserver, toString("SERVER:PACK_ANIMAL:BEAST%d:UID",i));
pIM->addDBObserver(&AnimalStatusObserver, toString("SERVER:PACK_ANIMAL:BEAST%d:STATUS",i));
}
}// initialize //
//-----------------------------------------------
// release :
// Free the class and all the components.
//-----------------------------------------------
void CEntityManager::release()
{
_LastEntityUnderPos= NULL;
// Remove all entities.
for(uint i=0; i<_Entities.size(); ++i)
{
if(_Entities[i])
{
// remove from fx manager
if (_Entities[i]->supportGroundFX())
{
_GroundFXManager.remove(_EntityGroundFXHandle[i]);
}
delete _Entities[i];
_Entities[i] = 0;
}
}
UserEntity = NULL;
// Clear the list.
_Entities.clear();
// Clean the backuped list.
_BackupedChanges.clear();
_GroundFXManager.reset();
}// release //
//-----------------------------------------------
void CEntityManager::reinit()
{
release();
initialize(_NbMaxEntity);
}
CShapeInstanceReference CEntityManager::createInstance(const string& shape, const CVector &pos, const string& text, const string& url, bool bbox_active)
{
CShapeInstanceReference nullinstref(UInstance(), string(""), string(""));
if (!Scene) return nullinstref;
UInstance instance = Scene->createInstance(shape);
if (text.empty())
bbox_active = false;
CShapeInstanceReference instref = CShapeInstanceReference(instance, text, url, bbox_active);
if(!instance.empty())
{
_ShapeInstances.push_back(instref);
}
return instref;
}
bool CEntityManager::removeInstances()
{
if (!Scene) return false;
// Remove all instances.
for(uint i=0; i<_ShapeInstances.size(); ++i)
{
if (!_ShapeInstances[i].Instance.empty())
Scene->deleteInstance(_ShapeInstances[i].Instance);
}
_ShapeInstances.clear();
return true;
}
CShapeInstanceReference CEntityManager::getShapeInstanceUnderPos(float x, float y)
{
CShapeInstanceReference selectedInstance(UInstance(), string(""), string(""));
_LastInstanceUnderPos= NULL;
// If not initialised, return
if (_ShapeInstances.empty())
return selectedInstance;
// build the ray
CMatrix camMatrix = MainCam.getMatrix();
CFrustum camFrust = MainCam.getFrustum();
CViewport viewport = Driver->getViewport();
// Get the Ray made by the mouse.
CVector pos, dir;
viewport.getRayWithPoint(x, y, pos, dir, camMatrix, camFrust);
// Normalize the direction.
dir.normalize();
// **** Get instances with box intersecting the ray.
float bestDist = 255;
for(uint i=0; i<_ShapeInstances.size(); i++)
{
if (_ShapeInstances[i].BboxActive)
{
H_AUTO(RZ_Client_GEUP_box_intersect)
// if intersect the bbox
NLMISC::CAABBox bbox;
//= _ShapeInstances[i].SelectionBox;
_ShapeInstances[i].Instance.getShapeAABBox(bbox);
if (bbox.getCenter() == CVector::Null)
{
bbox.setMinMax(CVector(-0.3f, -0.3f, -0.3f)+_ShapeInstances[i].Instance.getPos(), CVector(0.3f, 0.3f, 0.3f)+_ShapeInstances[i].Instance.getPos());
}
else
{
bbox.setMinMax((bbox.getMin()*_ShapeInstances[i].Instance.getScale().x)+_ShapeInstances[i].Instance.getPos(), (bbox.getMax()*_ShapeInstances[i].Instance.getScale().x)+_ShapeInstances[i].Instance.getPos());
}
if(bbox.intersect(pos, pos+dir*15.0f))
{
float dist = (bbox.getCenter()-pos).norm();
if (dist < bestDist)
{
selectedInstance = _ShapeInstances[i];
bestDist = dist;
}
}
}
}
return selectedInstance;
}
//-----------------------------------------------
// Create an entity according to the slot and the form.
// \param uint slot : slot for the entity.
// \param uint32 form : form to create the entity.
// \param TClientDataSetIndex : persitent id while the entity is connected.
// \return CEntityCL * : pointer on the new entity.
//-----------------------------------------------
CEntityCL *CEntityManager::create(uint slot, uint32 form, const TNewEntityInfo& newEntityInfo)
{
// DEBUG
if(verboseVP(NULL, form))
nlinfo("(%05d,%03d) EM:create: slot '%u': %s", sint32(T1%100000), NetMngr.getCurrentServerTick(), slot, CSheetId(form).toString().c_str());
// Check parameter : slot.
if(slot >= _NbMaxEntity)
{
nlwarning("EM:create: Cannot create the entity, the slot '%u' is invalid.", slot);
return 0;
}
else
{
// Slot 0 is for the user and so should be allocated only once (at beginning of main loop).
if( slot == 0 && _Entities[0] )
{
if (newEntityInfo.DataSetIndex != CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
{
// Store the dataSetId received
_Entities[0]->dataSetId(newEntityInfo.DataSetIndex);
}
// Store the alias (although there should not be one for the slot 0!)
_Entities[0]->npcAlias(newEntityInfo.Alias);
return 0;
}
}
// Remove the old one (except the user).
if(_Entities[slot])
{
nlwarning("EM:create: There is already an entity in the slot '%u' ! Old entity will be removed.", slot);
// remove from ground fx manager
// TODO : test if entity has ground fxs
if (_Entities[slot]->supportGroundFX())
{
_GroundFXManager.remove(_EntityGroundFXHandle[slot]);
}
delete _Entities[slot];
_Entities[slot] = 0;
}
// Check parameter : form.
CEntitySheet *entitySheet = SheetMngr.get((CSheetId)form);
if(entitySheet == 0)
{
nlwarning("EM:create: Attempt on create an entity with a bad form number %d (%s) for the slot '%d' trying to compute the default one.", form, ((CSheetId)form).toString().c_str(), slot);
CSheetId defaultEntity;
if(defaultEntity.buildSheetId(ClientCfg.DefaultEntity)==false)
{
nlwarning("EM:create: The default entity (%s) is not in the sheetid.bin.", ClientCfg.DefaultEntity.c_str());
return 0;
}
entitySheet = SheetMngr.get(defaultEntity);
if(entitySheet == 0)
{
nlwarning("EM:create: The default entity (%s) is not in the sheet manager.", ClientCfg.DefaultEntity.c_str());
return 0;
}
}
// Create the entity according to the type.
switch(entitySheet->type())
{
case CEntitySheet::RACE_STATS:
case CEntitySheet::CHAR:
if (slot == 0)
{
nlassert (UserEntity == NULL);
UserEntity = new CUserEntity;
_Entities[slot] = UserEntity;
}
else
{
_Entities[slot] = new CPlayerCL;
}
break;
case CEntitySheet::FAUNA:
{
CCharacterSheet *sheet = NLMISC::safe_cast<CCharacterSheet *>(entitySheet);
if (!sheet->R2Npc) _Entities[slot] = new CCharacterCL;
else _Entities[slot] = new CPlayerR2CL;
}
break;
case CEntitySheet::FLORA:
_Entities[slot] = new CCharacterCL;
break;
case CEntitySheet::FX:
_Entities[slot] = new CFxCL;
break;
case CEntitySheet::ITEM:
_Entities[slot] = new CItemCL;
break;
case CEntitySheet::FORAGE_SOURCE:
_Entities[slot] = new CForageSourceCL;
break;
default:
pushDebugStr(NLMISC::toString("Unknown Form Type '%d' -> entity not created.", entitySheet->type()));
break;
}
// If the entity has been right created.
if(_Entities[slot])
{
// Set the sheet Id.
_Entities[slot]->sheetId((CSheetId)form);
// Set the slot.
_Entities[slot]->slot(slot);
// Set the DataSet Index. AFTER slot(), so bar manager is correctly init
_Entities[slot]->dataSetId(newEntityInfo.DataSetIndex);
// Set the Mission Giver Alias
_Entities[slot]->npcAlias(newEntityInfo.Alias);
// Build the entity from a sheet.
if(_Entities[slot]->build(entitySheet))
{
// Apply properties backuped;
applyBackupedProperties(slot);
// register to the ground fx manager
if(_Entities[slot]->supportGroundFX())
{
_EntityGroundFXHandle[slot] = _GroundFXManager.add(_Entities[slot]);
}
}
// Entity is not valid -> REMOVE IT
else
{
// Everyone except the User
if(slot != 0)
{
nlwarning("EM:%d: Cannot build the Entity -> REMOVE IT", slot);
delete _Entities[slot];
_Entities[slot] = 0;
}
// The User
else
nlerror("EM: Cannot build the User");
}
}
// Entity Not Allocated
else
pushDebugStr(NLMISC::toString("Cannot Allocated the Entity in the slot '%d'.", slot));
// Log problems about the entity creation.
flushDebugStack(NLMISC::toString("Create Entity in slot '%d' with Form '%s' :", slot, ((CSheetId)form).toString().c_str()));
// Return a pointer on the entity created.
return _Entities[slot];
}// create //
//-----------------------------------------------
// remove :
// Delete an entity.
// \todo GUIGUI : rename into free.
// \todo GUIGUI : finish the function.
//-----------------------------------------------
bool CEntityManager::remove(uint slot, bool warning)
{
// DEBUG
if(verboseVP(NULL))
nlinfo("EM:remove: slot '%u'.", slot);
// Check parameter : slot.
if(slot >= _NbMaxEntity)
{
nlwarning("CEntityManager::remove : Attempt on delete a bad slot (slot %d)", slot);
return false;
}
// Do not delete the user.
if(slot == 0)
{
nlwarning("CEntityManager::remove : Cannot remove the entity in the slot 0 (user slot).");
return false;
}
// Slot not allocated.
if(_Entities[slot] == 0)
{
if(warning)
{
nlwarning("CEntityManager::remove : Attempt on delete the slot '%d' that is not allocated.", slot);
return false;
}
}
// Remove the entity from others target.
for(uint i=0; i<_Entities.size(); ++i)
{
// This entity is not allocated.
if(_Entities[i] == 0)
continue;
// Inform about the slot of the entity that will be removed.
_Entities[i]->slotRemoved(slot);
}
// remove ground fx
if(_Entities[slot] != 0)
{
if (_Entities[slot]->supportGroundFX())
{
_GroundFXManager.remove(_EntityGroundFXHandle[slot]);
}
}
// notify the projectile manager that entity has been removed
CProjectileManager::getInstance().entityRemoved(slot);
// notify the Bar Manager
if(_Entities[slot])
CBarManager::getInstance()->delEntity(_Entities[slot]->slot());
// previous UnderPos?
if(_LastEntityUnderPos==_Entities[slot])
_LastEntityUnderPos= NULL;
// Free the slot.
delete _Entities[slot];
_Entities[slot] = 0;
// Done.
return true;
}// remove //
//-----------------------------------------------
// removeCollision :
// Remove the collision for all entities.
//-----------------------------------------------
void CEntityManager::removeCollision()
{
const uint nbEntities = (uint)_Entities.size();
for(uint i=0; i<nbEntities; ++i)
{
// Is the entity allocated.
if(_Entities[i] == 0)
continue;
// Remove the entity primitive.
_Entities[i]->removePrimitive();
// Remove the collision entity.
_Entities[i]->removeCollisionEntity();
}
}// removeCollision //
//-----------------------------------------------
// reloadAnims :
// Re-load animations (remove and load).
//-----------------------------------------------
void CEntityManager::reloadAnims()
{
for(uint i=0; i<_Entities.size(); ++i)
{
if(_Entities[i])
{
// Get a reference on the current entity.
CEntityCL &entity = *(_Entities[i]);
// Change the playlist
entity.buildPlaylist();
}
}
}// reloadAnims //
//-----------------------------------------------
// entity :
// Get a pointer on an entity according to the asked slot.
// \param uint slot : the asked slot.
// \return CEntityCL * : pointer on the entity or 0.
//-----------------------------------------------
CEntityCL *CEntityManager::entity(uint slot)
{
// Return 0 if the slot is the INVALID_SLOT
if(slot==CLFECOMMON::INVALID_SLOT)
return 0;
// Check parameter : slot.
if(slot >= _Entities.size())
{
nlwarning("EM:entity: slot '%u' is invalid.", slot);
if(ClientCfg.Check)
nlstop;
return 0;
}
// Return the entity pointer.
return _Entities[slot];
}// entity //
//-----------------------------------------------
// entitiesNearDoors :
// Return if there is an entity near a door.
// \param float openingDist : near is when you are under the 'openingDist'.
// \param const CVector& posDoor1 : first door position.
// \param const CVector& posDoor2 : second door position.
// \return bool ; 'true' if any entity is near one of the door.
//-----------------------------------------------
bool CEntityManager::entitiesNearDoors(float openingDist, const CVector& posDoor1, const CVector& posDoor2)
{
for(uint i=0; i<_NbMaxEntity; ++i)
{
// Is the entity allocated.
if(_Entities[i] == 0)
continue;
// Get a reference on the current entity.
CEntityCL &entity = *(_Entities[i]);
// If the entity is close enough from the door -> return true.
if( ((entity.pos() - posDoor1).sqrnorm() < openingDist)
|| ((entity.pos() - posDoor2).sqrnorm() < openingDist) )
return true;
}
// No Entity near the door.
return false;
}// entitiesNearDoors //
//-----------------------------------------------
// getEntityListForSelection
//-----------------------------------------------
void CEntityManager::getEntityListForSelection(std::vector<CEntityCL*> &entities, uint flags)
{
// According to the view (first or third person), the user can or cannot be selected.
entities.clear();
uint firstEntity = (flags&CEntityFilterFlag::NotUser)?1:0;
for(uint i=firstEntity; i<_NbMaxEntity; ++i)
{
// Is the entity allocated and not user mount.
if(_Entities[i] == 0 || i==UserEntity->mount())
continue;
// If entity unselectable, skip
if(!_Entities[i]->properties().selectable())
continue;
// Apply each filter
if ( (flags&CEntityFilterFlag::Friend) && !_Entities[i]->isFriend() )
continue;
if ( (flags&CEntityFilterFlag::Enemy) && !_Entities[i]->isEnemy() )
continue;
if ( (flags&CEntityFilterFlag::Alive) && _Entities[i]->isReallyDead() )
continue;
if ( (flags&CEntityFilterFlag::Dead) && !_Entities[i]->isReallyDead() )
continue;
if ( (flags&CEntityFilterFlag::Player) && !_Entities[i]->isPlayer() )
continue;
if ( (flags&CEntityFilterFlag::NonPlayer) && _Entities[i]->isPlayer() )
continue;
// Insert every entity in the valid list.
entities.push_back(_Entities[i]);
}
}
//-----------------------------------------------
// getEntityUnderPos :
// Get the entity under the (2d) position. Return NULL if not entity under this position.
//-----------------------------------------------
struct CSortEntity
{
CEntityCL *Entity;
float Depth;
bool operator<(const CSortEntity &o) const
{
return Depth<o.Depth;
}
};
CEntityCL *CEntityManager::getEntityUnderPos(float x, float y, float distSelection, bool &isPlayerUnderCursor)
{
H_AUTO (RZ_Client_getEntityUnderPos )
uint i;
// valid only if bbox still intersect
CEntityCL *precEntityUnderPos= _LastEntityUnderPos;
bool precEntityUnderPosValid= false;
// reset result
isPlayerUnderCursor= false;
_LastEntityUnderPos= NULL;
// If not initialised, return
if (_Entities.empty())
return NULL;
// **** list of valid entities to test
static vector<CEntityCL*> validEntities;
uint filterFlags= CEntityFilterFlag::NoFilter;
getEntityListForSelection(validEntities, filterFlags);
// build the ray
CMatrix camMatrix = MainCam.getMatrix();
CFrustum camFrust = MainCam.getFrustum();
CViewport viewport = Driver->getViewport();
// Get the Ray made by the mouse.
CVector pos, dir;
viewport.getRayWithPoint(x, y, pos, dir, camMatrix, camFrust);
// Normalize the direction.
dir.normalize();
// **** Get entities with box intersecting the ray.
static vector<CSortEntity> intersectedEntities;
intersectedEntities.clear();
for(i=0;i<validEntities.size();i++)
{
H_AUTO(RZ_Client_GEUP_box_intersect)
CEntityCL *entity = validEntities[i];
// if entity not visible, skip
if(entity->getLastClip())
continue;
// if intersect the bbox
NLMISC::CAABBox bbox = entity->selectBox();
if(bbox.intersect(pos, pos+dir*distSelection))
{
// add this entity to the list of possible entities
CSortEntity e;
e.Entity= entity;
e.Depth= (bbox.getCenter()-pos).norm();
intersectedEntities.push_back(e);
// is it the last entity under pos?
if(entity==precEntityUnderPos)
precEntityUnderPosValid= true;
}
}
// if no intersected entities, quit
if(intersectedEntities.empty())
return NULL;
// Compute startDistBox: nearest entity distance, but the user
float startDistBox;
if(intersectedEntities[0].Entity==UserEntity)
{
// if the nearest entity is the user, set res
isPlayerUnderCursor= true;
// if only player intersected, return NULL!
if(intersectedEntities.size()==1)
return NULL;
// so take the second for startDistBox
startDistBox= intersectedEntities[1].Depth;
}
else
{
// ok, take it.
startDistBox= intersectedEntities[0].Depth;
}
// **** get best entity according to distance face-camera or box-ray if no face intersection
CEntityCL *entitySelected= NULL;
float bestDistBox= FLT_MAX;
float bestDistZ= FLT_MAX;
for(i=0;i<intersectedEntities.size();i++)
{
CEntityCL *entity = intersectedEntities[i].Entity;
const NLMISC::CAABBox &bbox = entity->selectBox();
// If this entity is the UserEntity, skip!!
if(entity==UserEntity)
continue;
// if entity skeleton model was clipped, skip
USkeleton *skeleton= entity->skeleton();
if(!ClientCfg.Light && skeleton && !skeleton->getLastClippedState())
continue;
H_AUTO(RZ_Client_GEUP_face_intersect)
// *** Try get face-intersection, result in distZ
// if the entity support fast and precise intersection (and if it succeeds)
bool trueIntersectComputed= false;
float dist2D, distZ;
if(!ClientCfg.Light)
{
if(skeleton)
{
if(skeleton->supportFastIntersect() && skeleton->fastIntersect(pos, dir, dist2D, distZ, false))
trueIntersectComputed= true;
}
// get the intersection with the instance (bot object)
else if(!entity->instances().empty() && !entity->instances()[0].Current.empty())
{
UInstance inst= entity->instances()[0].Current;
if(inst.supportFastIntersect() && inst.fastIntersect(pos, dir, dist2D, distZ, false))
trueIntersectComputed= true;
}
}
// if true face-intersection not found
if(!trueIntersectComputed)
{
/*
this happens especially for Forage Source. but could happens for anyhting else
In this case, estimate face-instersection, with box:
Suppose full intersection, if the ray is in the 1/3 of the bbox
*/
// clip the ray with the box
CVector a= pos, b= pos+dir*distSelection;
if(!bbox.clipSegment(a, b))
continue;
// take the middle of the clipped segment. suppose that this middle is the "nearest ray point to center"
// This is false, but gives better results.
CVector m= (a+b)/2;
// Suppose full intersection, if the ray is in the 1/3 of the bbox
CVector itToCenter= m-bbox.getCenter();
itToCenter.maxof(itToCenter, -itToCenter);
CVector smallBoxHS= bbox.getHalfSize()*0.3f;
smallBoxHS.maxof(smallBoxHS, -smallBoxHS);
if(itToCenter.x<=smallBoxHS.x && itToCenter.y<=smallBoxHS.y && itToCenter.z<=smallBoxHS.z)
{
dist2D= 0;
distZ= (m-pos).norm();
}
else
{
// no intersection
dist2D= FLT_MAX;
distZ= 0;
}
}
// else it's ok, dist2D and distZ are computed
// *** if intersect face, then take the best face-intersection, else use box-ray cost
// true face-col found?
if(dist2D==0)
{
// yes, get the nearest
if(distZ<bestDistZ)
{
bestDistBox= 0;
bestDistZ= distZ;
entitySelected= entity;
}
}
// else
else
{
// if a true face-intersection has not been found for others entities
if(bestDistZ==FLT_MAX)
{
// get the "distance to camera" contribution.
CVector c= bbox.getCenter();
float distCamCost= intersectedEntities[i].Depth;
// get relative to the nearest intersected entity
distCamCost-= startDistBox;
// get the ratio "how many the ray is in the bbox"
CVector a= pos, b= pos+dir*distSelection;
bbox.clipSegment(a, b);
// take the middle of the clipped segment. suppose that this middle is the "nearest ray point to center"
// This is false, but gives better results.
CVector m= (a+b)/2;
// get the distance to center. NB: small entities are preferred since smaller mean lower cost
float outBBoxCost= (m-c).norm();
// the final cost is a weighted sum of the both. NB: distCamCost is in meter,
// and outBBBoxCost is meters. Hence ClientCfg.SelectionOutBBoxWeight is a factor
float boxCost= distCamCost + outBBoxCost * ClientCfg.SelectionOutBBoxWeight;
// take the lowest cost
if(boxCost<bestDistBox)
{
entitySelected= entity;
bestDistBox= boxCost;
}
}
}
}
// If precise intersection not found
if(bestDistZ==FLT_MAX)
{
// if the last entity under pos is valid, prefer it among all other approximate ones
if(precEntityUnderPos && precEntityUnderPosValid)
entitySelected= precEntityUnderPos;
}
// return the best entity
_LastEntityUnderPos= entitySelected;
return entitySelected;
}// getEntityUnderPos //
//-----------------------------------------------
// getEntityInCamera
//-----------------------------------------------
CEntityCL *CEntityManager::getEntityInCamera(uint flags, float distSelection, CLFECOMMON::TCLEntityId precEntity)
{
H_AUTO (RZ_Client_getEntityInCamera )
// If not initialised, return
if (_Entities.empty())
return NULL;
// list of valid entities
static vector<CEntityCL*> validEntitiesTmp, validEntities;
getEntityListForSelection(validEntitiesTmp, flags);
// Remove entities not selectable by space key
uint i;
validEntities.clear();
for (i=0 ; i<validEntitiesTmp.size() ; i++)
{
CCharacterCL *entity = dynamic_cast<CCharacterCL*>(validEntitiesTmp[i]);
if ((entity == NULL) || (entity && entity->isSelectableBySpace()))
validEntities.push_back(entity);
}
// Build the camera pyramid
CMatrix camMatrix = MainCam.getMatrix();
CFrustum camFrust = MainCam.getFrustum();
static vector<CPlane> camPyramid;
// No need to use worldMatrix. NB: not setuped if ClientLight.
MainCam.buildCameraPyramid(camPyramid, false);
// list of entities in screen
static vector<CSortEntity> screenEntities;
screenEntities.clear();
// compute distance related to the user pos (not camera one).
CVector userPos = UserEntity->pos();
// prefer take the direction of the camera (can select backward for instance with camera rotation)
CVector userDir = View.currentView().normed();
// Get all entity in this pyramid, and in the dist selection
for(i=0;i<validEntities.size();i++)
{
CEntityCL *entity= validEntities[i];
const NLMISC::CAABBox &b = entity->selectBox();
bool isIn= true;
for(uint j=0;j<camPyramid.size();j++)
{
if( !b.clipBack(camPyramid[j]) )
{
isIn= false;
break;
}
}
// if In the pyramid
if(isIn)
{
CVector dirToEntity= b.getCenter()-userPos;
CSortEntity eSelect;
eSelect.Entity= entity;
eSelect.Depth= dirToEntity.norm();
// if in max distance
if(eSelect.Depth<distSelection)
{
// The lower, the more the influence of direction (minimum should be 1)
const float dirInfluence= 1.1f;
// modulate the depth with dot3: force take the most in front of user.
if(eSelect.Depth>0)
dirToEntity/= eSelect.Depth;
eSelect.Depth*= dirInfluence-dirToEntity*userDir;
// append to sort list
screenEntities.push_back(eSelect);
}
}
}
// No one in screen?
if(screenEntities.empty())
return NULL;
// sort them increasingly
sort(screenEntities.begin(), screenEntities.end());
// Try to find the precEntity in this list
uint entitySelected= 0;
if(precEntity!=CLFECOMMON::INVALID_SLOT)
{
for(i=0;i<screenEntities.size();i++)
{
// if found the precEntity, get the farther one
if(screenEntities[i].Entity->slot()==precEntity)
{
entitySelected= i+1;
break;
}
}
// reset to 0 if: no more entities, or if the max cycle is reached
if(entitySelected>=screenEntities.size() || entitySelected>=ClientCfg.SpaceSelectionMaxCycle)
entitySelected= 0;
}
// found!
return screenEntities[entitySelected].Entity;
}
//-----------------------------------------------
// changeContinent :
// Continent has changed.
//-----------------------------------------------
void CEntityManager::changeContinent()
{
// Re-create entities primitive.
for(uint i=0; i<_NbMaxEntity; ++i)
{
// Is the entity allocated.
if(_Entities[i] == 0)
continue;
// Compute the new primitive.
_Entities[i]->computePrimitive();
// Compute the new collision entity.
_Entities[i]->computeCollisionEntity();
}
}// changeContinent //
//-----------------------------------------------
// updatePreCamera :
// Update entites before the camera position is computed.
// This update the entites position. Evaluate collisions. Compte final world position.
//-----------------------------------------------
void CEntityManager::updatePreCamera()
{
H_AUTO ( RZ_Client_Entity_Mngr_Update_Pre_Cam )
uint i;
// Build an entity list..
_ActiveEntities.reserve (_Entities.size ());
_ActiveEntities.clear ();
// Reset Counters
resetCounters();
// Update entities position.
for(i=0; i<_NbMaxEntity; ++i)
{
// Is the entity allocated.
CEntityCL *entity = _Entities[i];
if(entity == 0)
continue;
// Count Entities
++_EntitiesAllocated;
switch(entity->Type)
{
case CEntityCL::User:
++_NbUser; break;
case CEntityCL::Player:
++_NbPlayer; break;
/*case CEntityCL::NPC:
case CEntityCL::Fauna:
case CEntityCL::Entity:
case CEntityCL::ForageSource:*/
default:
++_NbChar; break;
}
// Update the list of Active Entities
_ActiveEntities.push_back (CEntityReference (i, entity));
}
// Adjust the orientation of the NPC in trade with the user.
if(UserEntity->trader() != CLFECOMMON::INVALID_SLOT)
{
CEntityCL * trader = _Entities[UserEntity->trader()];
if(trader)
trader->front(UserEntity->pos() - trader->pos());
}
// Adjust the orientation of the NPC in dyn chat with the user.
if(UserEntity->interlocutor() != CLFECOMMON::INVALID_SLOT)
{
CEntityCL * interlocutor = _Entities[UserEntity->interlocutor()];
if(interlocutor)
interlocutor->front(UserEntity->pos() - interlocutor->pos());
}
// Update entities position except the User
for(i=1; i<_EntitiesAllocated; ++i)
{
CEntityReference &activeEntity = _ActiveEntities[i];
// Get a poiner on the entity target
CEntityCL *target = entity(activeEntity.Entity->targetSlot());
// Update the entity.
activeEntity.Entity->updatePreCollision(T1, target);
}
// USER
{
// Get a poiner on the entity target
CEntityCL *target = entity(UserEntity->targetSlot());
// update user behaviour/speed/heading/vectorUp/position/bodyHeading
UserEntity->applyMotion(target);
// Update the entity.
UserEntity->updatePreCollision(T1, target);
}
// Update PACS
if(PACS)
{
// Time since last Frame
double DTEval = ((float)(T1-T0))*0.001f;
PACS->evalCollision(DTEval, staticWI); // Eval the static world.
PACS->evalCollision(DTEval, dynamicWI); // Eval the dynamic world.
getDoorManager().getPACSTriggers(); // Copy triggers to be used in update
managePACSTriggers();
UserEntity->checkPos();
}
// Update entities position.
for(i=0; i<_EntitiesAllocated; ++i)
{
CEntityReference &activeEntity = _ActiveEntities[i];
// Get a poiner on the entity target
CEntityCL *target = entity(activeEntity.Entity->targetSlot());
// Update the entity.
activeEntity.Entity->updatePostCollision(T1, target);
}
// User Orientation
UserEntity->applyForceLook();
getDoorManager().update(); // Check for trigger to open/close doors
MissionTargetObserver.update();
}// updatePreCamera //
//-----------------------------------------------
// updatePostCamera :
// Update the entity (position\animation).
// Clip the primitives
// Update visual entites parameters for clipped and non-clipped primitives
// This update the entites position.
//-----------------------------------------------
void CEntityManager::updatePostCamera(uint clippedUpdateTime, const std::vector<CPlane> &clippingPlanes, const CVector &camPos)
{
H_AUTO ( RZ_Client_Entity_Mngr_Update_Post_Cam )
// Build a non clipped entity list..
_VisibleEntities.reserve (_Entities.size ());
_VisibleEntities.clear ();
static bool firstTime = true;
// Clip entities position.
uint i;
for(i=0; i<_EntitiesAllocated; ++i)
{
CEntityReference &activeEntity = _ActiveEntities[i];
// Get a poiner on the entity target
CEntityCL *target = entity(activeEntity.Entity->targetSlot());
// Clip it
if (!activeEntity.Entity->clipped(clippingPlanes, camPos)
|| (R2::getEditor().getSelectedInstance() && R2::getEditor().getSelectedInstance()->getEntity()==activeEntity.Entity))
{
// Add to visible primitives
_VisibleEntities.push_back (activeEntity);
activeEntity.Entity->setLastClip(false);
activeEntity.Entity->updateVisible (T1, target);
}
else
{
activeEntity.Entity->setLastClip(true);
if (firstTime)
{
// Update texture Async Loading
activeEntity.Entity->updateAsyncTexture();
// Update lod Texture
activeEntity.Entity->updateLodTexture();
}
// Update this clipped primitive at this time ?
if ((activeEntity.Slot&RZ_CLIPPED_UPDATE_TIME_MASK) == clippedUpdateTime)
{
activeEntity.Entity->updateSomeClipped (T1, target);
}
// Update clipped primitives
activeEntity.Entity->updateClipped (T1, target);
}
}
// Update visible entities post positions.
const uint count = (uint)_VisibleEntities.size ();
for(i=0; i<count; ++i)
{
CEntityReference &visibleEntity = _VisibleEntities[i];
// Get a poiner on the entity target
CEntityCL *target = entity(visibleEntity.Entity->targetSlot());
//
visibleEntity.Entity->updateVisiblePostPos(T1, target);
}
// update ground fx
_GroundFXManager.update(NLMISC::CVectorD(camPos));
firstTime = false;
}// updatePostCamera //
//-----------------------------------------------
// updatePostRender :
// Update entites after the render 3D.
//-----------------------------------------------
void CEntityManager::updatePostRender()
{
H_AUTO_USE ( RZ_Client_Update_Post_Render )
TextContext->setHotSpot(UTextContext::MiddleMiddle);
TextContext->setFontSize(ClientCfg.NameFontSize);
CRGBA color;
const uint activeCount = (uint)_ActiveEntities.size ();
uint i;
for(i=0; i<activeCount; i++)
{
CEntityReference &visibleEntity = _ActiveEntities[i];
// Update in-scene interface
visibleEntity.Entity->updateAllPostRender ();
}
const uint count = (uint)_VisibleEntities.size ();
for(i=0; i<count; ++i)
{
CEntityReference &visibleEntity = _VisibleEntities[i];
// Update Visible Entities after the render.
visibleEntity.Entity->updateVisiblePostRender();
// Draw the entity Path.
if(ClientCfg.ShowPath)
visibleEntity.Entity->drawPath();
// Draw the selection box.
if(ClientCfg.DrawBoxes)
visibleEntity.Entity->drawBox();
// Display Modifiers (Dmgs/heals).
if(1)
visibleEntity.Entity->displayModifiers();
}
// Flush any no more used Flying text. Must do it before interface display (to be sure texts are hid)
CInterfaceManager *pIM= CInterfaceManager::getInstance();
pIM->FlyingTextManager.releaseNotUsedFlyingText();
}// updatePostRender //
//-----------------------------------------------
// updateVisualProperty :
// Method to update the visual property 'prop' for the entity in 'slot'.
// \param uint slot : slot of the entity to update.
// \param uint prop : the property to udapte.
//-----------------------------------------------
void CEntityManager::updateVisualProperty(const NLMISC::TGameCycle &gameCycle, const uint &slot, const uint &prop, const NLMISC::TGameCycle &predictedInterval)
{
// INFO : log some debug information about visual properties.
if(verboseVP(NULL))
nlinfo("EM:updateVP: received prop '%d' for the slot '%d'.", prop, slot);
// Check parameter : slot.
if(slot >= _NbMaxEntity)
{
nlwarning("CEntityManager::updateVisualProperty : Slot '%d' is not valid.", slot);
return;
}
// Entity still not allocated -> backup values received for the entity.
if(_Entities[slot] == 0)
{
// INFO : log some debug information about visual properties.
if(verboseVP(NULL))
nlinfo("EM:updateVP: backup the property as long as the entity is not allocated.", prop, slot);
string propName = toString("SERVER:Entities:E%d:P%d", slot, prop);
TProperty propty;
propty.GC = gameCycle;
// propty.Value = IngameDbMngr.getProp(propName);
TBackupedChanges::iterator it = _BackupedChanges.find(slot);
// Entity does not have any changes backuped for the time.
if(it == _BackupedChanges.end())
{
TProperties propMap;
propMap.insert(make_pair(prop, propty));
_BackupedChanges.insert(make_pair(slot, propMap));
}
// Entity already have some changes backuped.
else
{
TProperties &properties = (*it).second;
TProperties::iterator itProp = properties.find(prop);
// This properties is still not backuped for this entity.
if(itProp == properties.end())
properties.insert(make_pair(prop, propty));
// There is already a backuped value
else
{
nlwarning("EM:updateVP:%d: property '%d' already backuped.", slot, prop);
(*itProp).second = propty;
}
}
}
// Entity already allocated -> apply values.
else
{
// Call the method from the entity to update the visual property.
_Entities[slot]->updateVisualProperty(gameCycle, prop, predictedInterval);
}
}// updateVisualProperty //
//-----------------------------------------------
// applyBackupedProperties :
//-----------------------------------------------
void CEntityManager::applyBackupedProperties(uint slot)
{
TBackupedChanges::iterator it = _BackupedChanges.find(slot);
if(it != _BackupedChanges.end())
{
TProperties &properties = (*it).second;
TProperties::iterator itProp = properties.begin();
while(itProp != properties.end())
{
_Entities[slot]->updateVisualProperty((*itProp).second.GC, (*itProp).first, 0);
++itProp;
}
_BackupedChanges.erase(it);
}
}// applyBackupedProperties //
//-----------------------------------------------
// writeEntities :
// Write a file with the position of all entities.
//-----------------------------------------------
void CEntityManager::writeEntities()
{
COFile f;
if(!f.open("entities.txt", false, true))
return;
string strTmp = "StartCommands = {\n";
f.serialBuffer((uint8*)strTmp.c_str(), (uint)strTmp.size());
const uint nb = (uint)_Entities.size();
for(uint i=1; i<nb; ++i)
{
if(_Entities[i])
{
strTmp = toString("\"%s\",\t\"%f\", \"%f\", \"%f\", \"%f\", \"%f\", \"%f\",\t// %3d\n", _Entities[i]->sheetId().toString().c_str(), _Entities[i]->pos().x, _Entities[i]->pos().y, _Entities[i]->pos().z, _Entities[i]->front().x, _Entities[i]->front().y, _Entities[i]->front().z, i);
f.serialBuffer((uint8*)strTmp.c_str(), (uint)strTmp.size());
}
}
strTmp = "};\n";
f.serialBuffer((uint8*)strTmp.c_str(), (uint)strTmp.size());
// Close the File.
f.close();
}// writeEntities //
//-----------------------------------------------
// serial
// Serialize entities.
//-----------------------------------------------
void CEntityManager::serial(class NLMISC::IStream &f) throw(NLMISC::EStream)
{
// Get nb max entities possible.
f.serial(_NbMaxEntity);
if(f.isReading())
{
release();
initialize(_NbMaxEntity);
}
// f.serial(_EntitiesAllocated); no need to serialize this one except maybe to check.
// Serialize each entity.
const uint nb = (uint)_Entities.size();
for(uint i=0; i<nb; ++i)
{
NLMISC::CSheetId si;
if(!f.isReading())
{
if(_Entities[i])
si = _Entities[i]->sheetId();
else
si = NLMISC::CSheetId::Unknown;
}
// ...
f.serial(si);
// Create the entity.
if(f.isReading() && (si != CSheetId::Unknown))
{
TNewEntityInfo emptyEntityInfo;
emptyEntityInfo.reset();
create(i, si.asInt(), emptyEntityInfo);
}
// Get/Set entity state.
if(_Entities[i])
_Entities[i]->serial(f);
}
}// serial //
//-----------------------------------------------
// dump :
// // Dump entities state.
//-----------------------------------------------
void CEntityManager::dump(class NLMISC::IStream &f)
{
// Serialize the class.
serial(f);
}// dump //
//-----------------------------------------------
// dumpXML :
// Dump entities state (XML Format).
//-----------------------------------------------
void CEntityManager::dumpXML(class NLMISC::IStream &f)
{
// Start the opening of a new node named Identity
f.xmlPush("Entities");
const uint nb = (uint)_Entities.size();
for(uint i=0; i<nb; ++i)
{
// Add a comment
// f.xmlComment();//toString("Describ the entity in the slot %d.", i).c_str());
// Start the opening of a new node named Identity
f.xmlPush(toString("Entity%d", i).c_str());
if(_Entities[i])
{
// Open a new node header named Address
f.xmlPushBegin("Name");
// Set a property name
f.xmlSetAttrib ("string");
ucstring n = _Entities[i]->getEntityName();
f.serial(n);
// Close the new node header
f.xmlPushEnd();
// Close the address node
f.xmlPop();
// Open a new node header named Address
f.xmlPushBegin("Sheet");
// Set a property name
f.xmlSetAttrib ("name");
string sheetName = _Entities[i]->sheetId().toString();
f.serial(sheetName);
// Close the new node header
f.xmlPushEnd();
// Close the address node
f.xmlPop();
// Open a new node header named Address
f.xmlPushBegin("Position");
// Close the new node header
f.xmlPushEnd();
f.serial(_Entities[i]->pos());
// Close the address node
f.xmlPop();
// Open a new node header named Address
f.xmlPushBegin("Front");
// Close the new node header
f.xmlPushEnd();
NLMISC::CVector front = _Entities[i]->front();
f.serial(front);
// Close the address node
f.xmlPop();
// Open a new node header named Address
f.xmlPushBegin("Mode");
// Set a property name
f.xmlSetAttrib ("name");
string mode = MBEHAV::modeToString(_Entities[i]->mode());
f.serial(mode);
// Set a property name
f.xmlSetAttrib ("num");
uint8 m = _Entities[i]->mode();
f.serial(m);
// Close the new node header
f.xmlPushEnd();
// Close the address node
f.xmlPop();
// Open a new node header named Address
f.xmlPushBegin("LastBehaviourPlayed");
// Set a property name
f.xmlSetAttrib ("name");
string beh = MBEHAV::behaviourToString(_Entities[i]->behaviour());
f.serial(beh);
// Set a property name
f.xmlSetAttrib ("num");
uint8 b = _Entities[i]->behaviour();
f.serial(b);
// Close the new node header
f.xmlPushEnd();
// Close the address node
f.xmlPop();
}
// Close the address node
f.xmlPop();
}
// Close the identity node
f.xmlPop();
}// dumpXML //
//-----------------------------------------------
CEntityCL *CEntityManager::getEntityByName (uint32 stringId) const
{
if (stringId)
{
uint i;
const uint count = (uint)_Entities.size();
for (i=0; i<count; i++)
{
if(_Entities[i])
if(_Entities[i]->getNameId() == stringId)
return _Entities[i];
}
}
return NULL;
}
//-----------------------------------------------
CEntityCL *CEntityManager::getEntityByName (const ucstring &name, bool caseSensitive, bool complete) const
{
ucstring source = name;
const uint size = (uint)source.size();
if (!caseSensitive)
{
uint j;
for (j=0; j<size; j++)
source[j] = tolower (source[j]);
}
uint i;
const uint count = (uint)_Entities.size();
uint selectedEntityId;
float selectedEntityDist = FLT_MAX; // No selected Entity
for (i=0; i<count; i++)
{
if(_Entities[i])
{
ucstring value = _Entities[i]->getDisplayName();
bool foundEntity = false;
uint j;
if (!caseSensitive)
{
for (j=0; j<value.size(); j++)
value[j] = tolower (value[j]);
}
// Complete test ?
if (complete)
{
if(value == source)
foundEntity = true;
}
else
{
if (value.size() >= size)
{
if (std::operator==(source, value.substr (0, size)))
foundEntity = true;
}
}
if (foundEntity)
{
const NLMISC::CVectorD &targetPosD = _Entities[i]->pos();
const NLMISC::CVectorD &userPosD = UserEntity->pos();
float deltaX = (float) targetPosD.x - (float) userPosD.x;
float deltaY = (float) targetPosD.y - (float) userPosD.y;
float dist = (float)sqrt(deltaX * deltaX + deltaY * deltaY);
if (dist < selectedEntityDist)
{
selectedEntityDist = dist;
selectedEntityId = i;
}
}
}
}
if (selectedEntityDist != FLT_MAX) // Entity found
return _Entities[selectedEntityId];
else
return NULL;
}
//-----------------------------------------------
CEntityCL *CEntityManager::getEntityByCompressedIndex(TDataSetIndex compressedIndex) const
{
if (compressedIndex != INVALID_DATASET_ROW)
{
uint i;
const uint count = (uint)_Entities.size();
for (i=0; i<count; i++)
{
if(_Entities[i])
if(_Entities[i]->dataSetId() == compressedIndex)
return _Entities[i];
}
}
return NULL;
}
//-----------------------------------------------
// managePACSTriggers :
// Manage PACS Triggers.
//-----------------------------------------------
void CEntityManager::managePACSTriggers()
{
uint i;
const uint nNbTrig = PACS->getNumTriggerInfo();
for(i=0; i<nNbTrig; ++i)
{
const NLPACS::UTriggerInfo &rTI = PACS->getTriggerInfo(i);
// Detect collisions between user and other entities, to not be block (only the user is a trigger so no need to check).
if(((rTI.Object0 & 0xFFFF) == UserDataEntity)
&& ((rTI.Object1 & 0xFFFF) == UserDataEntity))
{
UserEntity->startColTimer();
break;
}
}
// Stop Collision.
if(i >= nNbTrig)
UserEntity->stopColTimer();
}// managePACSTriggers //
//-----------------------------------------------
// removeColUserOther :
//
//-----------------------------------------------
void CEntityManager::removeColUserOther()
{
uint i;
const uint count = (uint)_Entities.size();
for(i=1; i<count; i++)
{
if(_Entities[i])
{
if(_Entities[i]->getPrimitive())
{
// remove collision only if the entity is Traversable (bot objects may not)
if(_Entities[i]->getTraversable())
_Entities[i]->getPrimitive()->setObstacle(false);
}
}
}
}// removeColUserOther //
//-----------------------------------------------
// restoreColUserOther :
//
//-----------------------------------------------
void CEntityManager::restoreColUserOther()
{
uint i;
const uint count = (uint)_Entities.size();
for(i=1; i<count; i++)
{
if(_Entities[i])
{
if(_Entities[i]->getPrimitive())
_Entities[i]->getPrimitive()->setObstacle(true);
}
}
}// restoreColUserOther //
//-----------------------------------------------
void CEntityManager::removeAllAttachedFX()
{
for(TEntities::iterator it = _Entities.begin(); it != _Entities.end(); ++it)
{
if (*it) (*it)->removeAllAttachedFX();
}
}
// ***************************************************************************
void CEntityManager::resetAllSoundAnimId()
{
for(uint i=0;i<_Entities.size();i++)
{
CEntityCL *ent= _Entities[i];
if(ent)
{
ent->resetAllSoundAnimId();
}
}
}
// ***************************************************************************
#define nldebugraw NLMISC::createDebug(), NLMISC::DebugLog->displayRawNL
// ***************************************************************************
void CEntityManager::startLogStageChange(sint32 currentGameCycle, sint64 currentLocalTime)
{
// first stop
stopLogStageChange();
// enable
_LogStageChange.Enabled= true;
_LogStageChange.StartGameCycle= currentGameCycle;
_LogStageChange.StartLocalTime= currentLocalTime;
_LogStageChange.LastEntityLoged= CLFECOMMON::INVALID_SLOT;
_LogStageChange.StageSet.clear();
nldebugraw("*** Start Loging Stage changes");
}
// ***************************************************************************
void CEntityManager::logStageChange(sint64 currentLocalTime)
{
if(!_LogStageChange.Enabled)
return;
// if still exist
CCharacterCL *ent= dynamic_cast<CCharacterCL*>(entity(WatchedEntitySlot));
if(ent)
{
// if the watched entity has been changed
if(WatchedEntitySlot!=_LogStageChange.LastEntityLoged)
{
_LogStageChange.LastEntityLoged= WatchedEntitySlot;
// backup set
_LogStageChange.StageSet= ent->_Stages._StageSet;
nldebugraw("*** Start Loging Stage changes for Entity %d", WatchedEntitySlot);
}
else
{
// can log it
sint32 recGcRef= _LogStageChange.StartGameCycle;
sint64 recTimeRef= _LogStageChange.StartLocalTime;
// compare 2 logs and display differences
CStageSet::TStageSet &oldStageSet= _LogStageChange.StageSet;
const CStageSet::TStageSet &newStageSet= ent->_Stages._StageSet;
// for pos log detail
CVectorD precNewPos= ent->pos();
CVectorD precOldPos= ent->pos();
// compare each new/old stage
CStageSet::TStageSet::const_iterator itOld= oldStageSet.begin();
CStageSet::TStageSet::const_iterator itNew= newStageSet.begin();
while(itOld!=oldStageSet.end() || itNew!=newStageSet.end())
{
// compare 2 iterators
sint signNewMinusOld;
if(itNew==newStageSet.end())
signNewMinusOld= +1;
else if(itOld==oldStageSet.end())
signNewMinusOld= -1;
else
{
if(itNew->first > itOld->first)
signNewMinusOld= +1;
else if(itNew->first < itOld->first)
signNewMinusOld= -1;
else
signNewMinusOld= 0;
}
// if signNewMinusOld= +1, it means an old exist, without a new (=> the stage has been removed)
if(signNewMinusOld==+1)
{
logPropertyChange(WatchedEntitySlot, itOld->second, CStage(), precOldPos, precNewPos, (sint32)itOld->first-recGcRef, currentLocalTime-recTimeRef);
// new prec pos (if any)
itOld->second.getPos(precOldPos);
itOld++;
}
// if signNewMinusOld= -1, it means an new exist, without an old (=> the stage has been added)
else if(signNewMinusOld==-1)
{
logPropertyChange(WatchedEntitySlot, CStage(), itNew->second, precOldPos, precNewPos, (sint32)itNew->first-recGcRef, currentLocalTime-recTimeRef);
// new prec pos (if any)
itNew->second.getPos(precNewPos);
itNew++;
}
// if ==0, means the stage exist in both, but properties set may be different
else
{
logPropertyChange(WatchedEntitySlot, itOld->second, itNew->second, precOldPos, precNewPos, (sint32)itNew->first-recGcRef, currentLocalTime-recTimeRef);
// new prec pos (if any)
itOld->second.getPos(precOldPos);
itNew->second.getPos(precNewPos);
itOld++;
itNew++;
}
}
// bkup the new stage set
oldStageSet= newStageSet;
}
}
// this entity might have been deleted, stop its log
else
{
_LogStageChange.LastEntityLoged= CLFECOMMON::INVALID_SLOT;
_LogStageChange.StageSet.clear();
}
}
// ***************************************************************************
void CEntityManager::logPropertyChange(CLFECOMMON::TCLEntityId who, const CStage &oldStage, const CStage &newStage,
const CVectorD &precOldPos, const CVectorD &precNewPos, sint32 relGameCycle, sint64 relLocalTime)
{
// For all properties of interest
CLFECOMMON::TPropIndex propLoged[]= {CLFECOMMON::PROPERTY_POSITION, CLFECOMMON::PROPERTY_ORIENTATION,
CLFECOMMON::PROPERTY_MODE, CLFECOMMON::PROPERTY_ENTITY_MOUNTED_ID, CLFECOMMON::PROPERTY_RIDER_ENTITY_ID,
CLFECOMMON::PROPERTY_BEHAVIOUR, CLFECOMMON::PROPERTY_TARGET_ID,
/*CLFECOMMON::PROPERTY_VISUAL_FX,
CLFECOMMON::PROPERTY_TARGET_LIST_0, CLFECOMMON::PROPERTY_TARGET_LIST_1,
CLFECOMMON::PROPERTY_TARGET_LIST_2, CLFECOMMON::PROPERTY_TARGET_LIST_3*/};
uint32 numProps= sizeof(propLoged) / sizeof(propLoged[0]);
for(uint i=0;i<numProps;i++)
{
pair<bool, sint64> oldProp= oldStage.property(propLoged[i]);
pair<bool, sint64> newProp= newStage.property(propLoged[i]);
// if change of the prop, log it
if((oldProp.first || newProp.first) && oldProp!=newProp)
{
// get the change reason
string reason;
if(!oldProp.first)
reason= "ADD";
else if(!newProp.first)
reason= "DEL";
else
reason= "CHG";
// get the value
sint64 value= newProp.second;
if(!newProp.first) value= oldProp.second;
string valStr;
// mode?
if(propLoged[i]==CLFECOMMON::PROPERTY_MODE)
{
valStr= MBEHAV::TMode((uint64)value).toString();
}
// behaviour
else if(propLoged[i]==CLFECOMMON::PROPERTY_BEHAVIOUR)
{
valStr= MBEHAV::CBehaviour((uint64)value).toString();
}
// mount
else if(propLoged[i]==CLFECOMMON::PROPERTY_ENTITY_MOUNTED_ID)
{
valStr= NLMISC::toString(value);
}
else if(propLoged[i]==CLFECOMMON::PROPERTY_RIDER_ENTITY_ID)
{
valStr= NLMISC::toString(value);
}
// Target
else if(propLoged[i]==CLFECOMMON::PROPERTY_TARGET_ID)
{
valStr= NLMISC::toString(value);
}
// Position
else if(propLoged[i]==CLFECOMMON::PROPERTY_POSITION)
{
// get the delta of move from previous pos stage
CVectorD pos;
float dist= 0.f;
if(newProp.first)
{
if(newStage.getPos(pos))
dist= float(CVectorD(pos.x-precNewPos.x, pos.y-precNewPos.y,0).norm());
valStr= toString("dst=%.1f pi=%d", dist, newStage.predictedInterval());
}
else
{
if(oldStage.getPos(pos))
dist= float(CVectorD(pos.x-precOldPos.x, pos.y-precOldPos.y,0).norm());
valStr= toString("dst=%.1f pi=%d", dist, oldStage.predictedInterval());
}
}
// Orientation
else if(propLoged[i]==CLFECOMMON::PROPERTY_ORIENTATION)
{
float rot= *(float*)(&value);
valStr= toString("%d", sint32(rot*180/Pi));
}
// display log
nldebugraw("** Entity %d: (gc=%3d,t=%3d) %s: %s %s", (sint32)who, relGameCycle, (sint32)relLocalTime, reason.c_str(), CLFECOMMON::getPropShortText(propLoged[i]), valStr.c_str());
}
}
}
// ***************************************************************************
void CEntityManager::stopLogStageChange()
{
_LogStageChange.Enabled= false;
nldebugraw("*** Stop Loging Stage changes");
}
// ***************************************************************************
bool CEntityManager::isLogingStageChange() const
{
return _LogStageChange.Enabled;
}
// ***************************************************************************
sint32 CEntityManager::getLogStageChangeStartCycle() const
{
if(isLogingStageChange())
return _LogStageChange.StartGameCycle;
else
return 0;
}
// ***************************************************************************
sint64 CEntityManager::getLogStageChangeStartLocalTime() const
{
if(isLogingStageChange())
return _LogStageChange.StartLocalTime;
else
return 0;
}
// ***************************************************************************
void CEntityManager::refreshInsceneInterfaceOfFriendNPC(uint slot)
{
CCharacterCL *entity = dynamic_cast<CCharacterCL*>(_Entities[slot]);
if (!entity)
return;
if (entity->canHaveMissionIcon()
&& entity->isFriend() // only valid once the Contextual property is received
)
{
entity->releaseInSceneInterfaces();
entity->buildInSceneInterface();
}
}