// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . ///////////// // INCLUDE // ///////////// #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(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(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::iterator it = _PendingMissionTitle.begin(); while (it != _PendingMissionTitle.end()) { std::list::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(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(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(parent->getNode(ICDBNode::CTextId("X"))); CCDBNodeLeaf *y = dynamic_cast(parent->getNode(ICDBNode::CTextId("Y"))); if (x && y) { CSmartPtr 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 _PendingMissionTitle; // std::set _AlreadyReceived; }; //----------------------------------------------- CMissionTargetObserver MissionTargetObserver; // *************************************************************************** class CTeamUIDObserver : public ICDBNode::IPropertyObserver { public : // From ICDBNode::IPropertyObserver virtual void update(ICDBNode* node ) { CCDBNodeLeaf *leaf = dynamic_cast(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(node); if (leaf) { // Must get the NAME leaf CCDBNodeBranch *parent= leaf->getParent(); if(parent) { leaf= dynamic_cast(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(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(node); if (leaf) { // Must get the NAME leaf CCDBNodeBranch *parent= leaf->getParent(); if(parent) { leaf= dynamic_cast(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; iaddDBObserver(&MissionTargetObserver, "SERVER:MISSIONS:"+toString(i)+":TARGET"+toString(j)+":TITLE"); } // Add an Observer to the Team database for (i=0; iaddDBObserver(&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; iaddDBObserver(&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(); _InstancesRemoved = true; return true; } bool CEntityManager::instancesRemoved() { bool instRemoved = _InstancesRemoved; _InstancesRemoved = false; return instRemoved; } 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(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; iremovePrimitive(); // 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 &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 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 intersectedEntities; intersectedEntities.clear(); for(i=0;igetLastClip()) 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;iselectBox(); // 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 validEntitiesTmp, validEntities; getEntityListForSelection(validEntitiesTmp, flags); // Remove entities not selectable by space key uint i; validEntities.clear(); for (i=0 ; i(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 camPyramid; // No need to use worldMatrix. NB: not setuped if ClientLight. MainCam.buildCameraPyramid(camPyramid, false); // list of entities in screen static vector 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;iselectBox(); bool isIn= true; for(uint j=0;j0) 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;islot()==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 &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; itargetSlot()); // 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; iupdateAllPostRender (); } const uint count = (uint)_VisibleEntities.size (); for(i=0; iupdateVisiblePostRender(); // 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 = 0; // 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; isheetId().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; isheetId(); 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; igetEntityName(); 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; igetNameId() == 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; jgetDisplayName(); bool foundEntity = false; uint j; if (!caseSensitive) { for (j=0; j= 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; idataSetId() == compressedIndex) return _Entities[i]; } } return NULL; } //----------------------------------------------- // managePACSTriggers : // Manage PACS Triggers. //----------------------------------------------- void CEntityManager::managePACSTriggers() { uint i; const uint nNbTrig = PACS->getNumTriggerInfo(); for(i=0; igetTriggerInfo(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; igetPrimitive()) { // 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; igetPrimitive()) _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(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 oldProp= oldStage.property(propLoged[i]); pair 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(_Entities[slot]); if (!entity) return; if (entity->canHaveMissionIcon() && entity->isFriend() // only valid once the Contextual property is received ) { entity->releaseInSceneInterfaces(); entity->buildInSceneInterface(); } }