// 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 . // misc #include "nel/misc/command.h" #include "nel/misc/vector_2d.h" #include "nel/misc/vectord.h" #include "nel/misc/hierarchical_timer.h" #include "nel/misc/path.h" #include "nel/misc/sheet_id.h" // #include "nel/ligo/primitive.h" #include "nel/ligo/ligo_config.h" // // game share #include "game_share/tick_event_handler.h" #include "game_share/ryzom_version.h" #include "game_share/mirrored_data_set.h" #include "game_share/ryzom_mirror_properties.h" #include "game_share/mode_and_behaviour.h" #include "game_share/synchronised_message.h" #include "server_share/used_continent.h" #include "server_share/pet_interface_msg.h" #include "game_share/utils.h" // // r2 share #include "server_share/r2_variables.h" // // local #include "gpm_service.h" #include "world_position_manager.h" #include "vision_delta_manager.h" #include "client_messages.h" #include "sheets.h" #ifdef NL_OS_WINDOWS # define NOMINMAX # include #endif // NL_OS_WINDOWS // force admin module to link in extern void admin_modules_forceLink(); void foo() { admin_modules_forceLink(); } using namespace std; using namespace NLMISC; using namespace NLNET; CGlobalPositionManagerService *pCGPMS= NULL; NLLIGO::CLigoConfig LigoConfig; /* * Get var from config file */ // Sint version bool getVarFromConfigFile(CConfigFile &cf, const string &name, sint32 &variable, sint32 defaultValue = 0) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? ptr->asInt() : defaultValue); return success; } // Uint version bool getVarFromConfigFile(CConfigFile &cf, const string &name, uint32 &variable, uint32 defaultValue = 0) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? ptr->asInt() : defaultValue); return success; } // Bool version bool getVarFromConfigFile(CConfigFile &cf, const string &name, bool &variable, bool defaultValue = false) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? (ptr->asInt() != 0) : defaultValue); return success; } // Float version bool getVarFromConfigFile(CConfigFile &cf, const string &name, float &variable, float defaultValue = 0.0f) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? ptr->asFloat() : defaultValue); return success; } // Double version bool getVarFromConfigFile(CConfigFile &cf, const string &name, double &variable, double defaultValue = 0) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? ptr->asDouble() : defaultValue); return success; } // String version bool getVarFromConfigFile(CConfigFile &cf, const string &name, string &variable, const string &defaultValue = string("")) { CConfigFile::CVar *ptr = cf.getVarPtr(name); bool success; variable = ((success = (ptr != NULL)) ? ptr->asString() : defaultValue); return success; } #define GET_VAR_FROM_CF(var, def) getVarFromConfigFile(ConfigFile, #var, var, def); /*-----------------------------------------------------------------*\ cbConnection \*-----------------------------------------------------------------*/ void cbConnection( const std::string &serviceName, NLNET::TServiceId serviceId, void *arg ) { } // cbConnection // TGameCycle TickStopGameCycle = 0; /*-----------------------------------------------------------------*\ cbDisconnection \*-----------------------------------------------------------------*/ void cbDisconnection( const std::string &serviceName, NLNET::TServiceId serviceId, void *arg ) { if( serviceName == string("TICKS") ) { TickStopGameCycle = CTickEventHandler::getGameCycle(); nlinfo( "tick stop -> %u", TickStopGameCycle ); } // remove all entities created by this service //CWorldPositionManager::removeAiVisionEntitiesForService( serviceId ); if (!IsRingShard) { CWorldPositionManager::triggerUnsubscribe(serviceId); } } // cbDisconnection // void cbEGSConnection( const std::string &serviceName, NLNET::TServiceId serviceId, void *arg ) { // The follwoing code commented out by Sadge because no refference could be found to reception of this message // // transmit the names of the used continents to the EGS // try // { // CUsedContinent::TUsedContinentCont continents = CUsedContinent::instance().getContinents(); // // vector< string > continentsNames; // for (uint i=0; iConfigFile.getVar("UsedContinents"); // for ( uint i = 0; (sint)isend( "EGS", msgout ); // } // catch(EUnknownVar &) // { // nlwarning(" UsedContinents not found, no continent used"); // } // send ESG all IA creatures //CWorldPositionManager::sendAgentsToEGS(); } // cbEGSConnection // /*-----------------------------------------------------------------*\ EVSConnection \*-----------------------------------------------------------------*/ void cbEVSConnection( const std::string &serviceName, NLNET::TServiceId serviceId, void *arg ) { EVSUp = true; } /*-----------------------------------------------------------------*\ EVSDisconnection \*-----------------------------------------------------------------*/ void cbEVSDisconnection( const std::string &serviceName, NLNET::TServiceId serviceId, void *arg ) { EVSUp = false; } /*--------------------------------------------------------------*\ cbSync() \*--------------------------------------------------------------*/ void cbSync() { // update World Position Manager time if( !TickStopGameCycle ) { if (!IsRingShard) { CWorldPositionManager::setCurrentTick( CTickEventHandler::getGameCycle() ); } } nlinfo( "Sync -> %u", CTickEventHandler::getGameCycle() ); } // cbSync // /* * Crash Callback */ string crashCallback() { return CWorldPositionManager::getPlayersPosHistory(); } /****************************************************************\ ************** callback table for input message **************** \****************************************************************/ TUnifiedCallbackItem CbArray[] = { { "CB_UNUSED", NULL } }; /* * Initialisation 2 */ void cbMirrorIsReady( CMirror *mirror ) { pCGPMS->initMirror(); } /****************************************************************\ init() \****************************************************************/ // init the service void CGlobalPositionManagerService::init() { setVersion (RYZOM_VERSION); // keep pointer on class pCGPMS = this; // set update time out setUpdateTimeout(100); CUnifiedNetwork::getInstance()->setServiceUpCallback( string("*"), cbConnection, 0); CUnifiedNetwork::getInstance()->setServiceUpCallback( "EGS", cbEGSConnection, 0); CUnifiedNetwork::getInstance()->setServiceUpCallback( "EVS", cbEVSConnection, 0); CUnifiedNetwork::getInstance()->setServiceDownCallback( string("*"), cbDisconnection, 0); CUnifiedNetwork::getInstance()->setServiceDownCallback( "EVS", cbEVSDisconnection, 0); uint32 WorldMapSizeX; uint32 WorldMapSizeY; uint32 VisionDistance; uint32 PrimitiveMaxSize; uint32 NbWorldImages; bool LoadPacsPrims; bool LoadPacsCol; // init the class transport system TRANSPORT_CLASS_REGISTER (CGPMPlayerPrivilegeInst); GET_VAR_FROM_CF(CheckPlayerSpeed, true); GET_VAR_FROM_CF(Verbose, false); GET_VAR_FROM_CF(WorldMapSizeX, 2100); GET_VAR_FROM_CF(WorldMapSizeY, 2970); GET_VAR_FROM_CF(VisionDistance, 250000); GET_VAR_FROM_CF(PrimitiveMaxSize, 8); GET_VAR_FROM_CF(NbWorldImages, 31); GET_VAR_FROM_CF(LoadPacsCol, false); GET_VAR_FROM_CF(LoadPacsPrims, true); CSheets::init(); // World Position Manager init if (!IsRingShard) { CWorldPositionManager::init(WorldMapSizeX, WorldMapSizeY, VisionDistance, PrimitiveMaxSize, (uint8)NbWorldImages, LoadPacsPrims, LoadPacsCol); } // Init ligo if (!LigoConfig.readPrimitiveClass ("world_editor_classes.xml", false)) { // Should be in l:\leveldesign\world_editor_files nlerror ("Can't load ligo primitive config file world_editor_classes.xml"); } /* // read the continent name translator map translator; { CConfigFile::CVar *v = IService::getInstance()->ConfigFile.getVarPtr("ContinentNameTranslator"); if (v) { for (sint i=0; isize()/2; ++i) { string s1, s2; s1 = v->asString(i*2); s2 = v->asString(i*2+1); translator[s1] = s2; } } } */ // todo: r2 GPMS doesn't read pacs for now - this will have to be fixed later if (!IsRingShard) { // init continents try { CUsedContinent::TUsedContinentCont continents = CUsedContinent::instance().getContinents(); // CConfigFile::CVar& cvUsedContinents = ConfigFile.getVar("UsedContinents"); // uint i; // for (i=0; (sint)i UsedContinents not found, no continent used"); } } NLLIGO::Register(); CMessages::init(); CClientMessages::init(); // if (!IsRingShard) { CWorldPositionManager::initPatatManager(); } // Init the mirror system vector datasetNames; datasetNames.push_back( "fe_temp" ); Mirror.init( datasetNames, cbMirrorIsReady, gpmsUpdate, cbSync ); Mirror.setServiceMirrorUpCallback( "EGS", cbEGSConnection, 0); setCrashCallback(crashCallback); if (IsRingShard) { // setup the R2 Vision object and move checker object pCGPMS->RingVisionDeltaManager=new CVisionDeltaManager; pCGPMS->RingVisionUniverse= new R2_VISION::CUniverse; pCGPMS->RingVisionUniverse->registerVisionDeltaManager(RingVisionDeltaManager); pCGPMS->MoveChecker= new CMoveChecker; } } // init // /* * Init after the mirror init */ void CGlobalPositionManagerService::initMirror() { /* // Allow to add a few entities manually (using the command addEntity) Mirror.declareEntityTypeOwner( RYZOMID::player, 10 ); Mirror.declareEntityTypeOwner( RYZOMID::npc, 500 ); */ DataSet = &(Mirror.getDataSet("fe_temp")); DataSet->declareProperty( "X", PSOReadWrite | PSONotifyChanges, "X" ); // group notification on X DataSet->declareProperty( "Y", PSOReadWrite | PSONotifyChanges, "X" ); // group notification on X DataSet->declareProperty( "Z", PSOReadWrite | PSONotifyChanges, "X" ); // group notification on X DataSet->declareProperty( "Theta", PSOReadWrite | PSONotifyChanges, "X" ); // group notification on X DataSet->declareProperty( "AIInstance", PSOReadOnly | PSONotifyChanges ); DataSet->declareProperty( "WhoSeesMe", PSOReadOnly | PSONotifyChanges ); DataSet->declareProperty( "LocalX", PSOReadWrite ); DataSet->declareProperty( "LocalY", PSOReadWrite ); DataSet->declareProperty( "LocalZ", PSOReadWrite ); DataSet->declareProperty( "TickPos", PSOReadWrite ); DataSet->declareProperty( "Sheet", PSOReadWrite ); DataSet->declareProperty( "Mode", PSOReadOnly /*| PSONotifyChanges*/ ); DataSet->declareProperty( "Behaviour", PSOReadOnly ); DataSet->declareProperty( "Cell", PSOReadWrite ); DataSet->declareProperty( "VisionCounter", PSOReadWrite ); DataSet->declareProperty( "CurrentRunSpeed", PSOReadOnly ); DataSet->declareProperty( "CurrentWalkSpeed", PSOReadOnly ); DataSet->declareProperty( "RiderEntity", PSOReadOnly ); DataSet->declareProperty( "Fuel", PSOReadOnly ); initRyzomVisualPropertyIndices( *DataSet ); Mirror.setNotificationCallback( IsRingShard? CGlobalPositionManagerService::ringShardProcessMirrorUpdates: CGlobalPositionManagerService::processMirrorUpdates ); } /****************************************************************\ update() \****************************************************************/ // main loop bool CGlobalPositionManagerService::update() { return true; } // update // void CGlobalPositionManagerService::_checkAddCharacterToRingAIInstance(sint32 aiInstance) { TCharactersPerAIInstance::iterator it= _CharactersPerAIInstance.find(aiInstance); if (it==_CharactersPerAIInstance.end()) { // this is the first character to be added to this instance so initialise it _CharactersPerAIInstance[aiInstance]=1; nlinfo("Creating new AIInstance in ring vision universe: %d",aiInstance); pCGPMS->RingVisionUniverse->createInstance(aiInstance,0); } else { ++it->second; nldebug("Increasing number of entities in aiInstance %d to %d",aiInstance,it->second); } } void CGlobalPositionManagerService::_checkRemoveCharacterFromRingAIInstance(sint32 aiInstance) { TCharactersPerAIInstance::iterator it= _CharactersPerAIInstance.find(aiInstance); BOMB_IF(it==_CharactersPerAIInstance.end(),NLMISC::toString("BUG: Can't find ai instance %d to remove character from",aiInstance),return); // decrement count of characters in ai instance and remove the instance if its empty --it->second; if (it->second<1) { // this was the last character in the instance so delete it nlinfo("removing AIInstance because last character has just left: %d",aiInstance); _CharactersPerAIInstance.erase(it); pCGPMS->RingVisionUniverse->removeInstance(aiInstance); } else { nldebug("Decreasing number of entities in aiInstance %d to %d",aiInstance,it->second); } } void CGlobalPositionManagerService::ringShardProcessMirrorUpdates() { // Process entities added to mirror TheDataset.beginAddedEntities(); TDataSetRow entityIndex = TheDataset.getNextAddedEntity(); while ( entityIndex != LAST_CHANGED ) { // lookup stats for the entity in the mirror const NLMISC::CEntityId &eid= TheDataset.getEntityId( entityIndex ); CMirrorPropValueRO aiInstance ( TheDataset, entityIndex, DSPropertyAI_INSTANCE ); CMirrorPropValueRO x ( TheDataset, entityIndex, DSPropertyPOSX ); CMirrorPropValueRO y ( TheDataset, entityIndex, DSPropertyPOSY ); CMirrorPropValueRO whoSeesMe ( TheDataset, entityIndex, DSPropertyWHO_SEES_ME ); R2_VISION::TInvisibilityLevel invisibility= (R2_VISION::TInvisibilityLevel)(whoSeesMe&((1<_checkAddCharacterToRingAIInstance(aiInstance); // if we have a player then set them up in the move checker if (eid.getType()==RYZOMID::player) { pCGPMS->MoveChecker->teleport(entityIndex, x, y, CTickEventHandler::getGameCycle()); } // add entity to the ring vision universe object pCGPMS->RingVisionUniverse->addEntity(entityIndex,aiInstance,x,y,invisibility,eid.getType()==RYZOMID::player); entityIndex = TheDataset.getNextAddedEntity(); } TheDataset.endAddedEntities(); // Process entities removed from mirror TheDataset.beginRemovedEntities(); CEntityId *id; entityIndex = TheDataset.getNextRemovedEntity( &id ); while ( entityIndex != LAST_CHANGED ) { // check that the character being removed exists in the r2VisionUniverse and get hold of their AIInstance const R2_VISION::SUniverseEntity* theEntity= pCGPMS->RingVisionUniverse->getEntity(entityIndex); BOMB_IF(theEntity==NULL,"Failed to identify the character that the mirror tells us is being removed",continue); uint32 aiInstance= theEntity->AIInstance; // remove entity from the ring vision universe object pCGPMS->RingVisionUniverse->removeEntity(entityIndex); // decrement the count of characters in the given instance and remove it if need be pCGPMS->_checkRemoveCharacterFromRingAIInstance(aiInstance); // prepare to iterate... entityIndex = TheDataset.getNextRemovedEntity( &id ); } TheDataset.endRemovedEntities(); // Process properties changed and notified in the mirror TPropertyIndex propIndex; TheDataset.beginChangedValues(); TheDataset.getNextChangedValue( entityIndex, propIndex ); while ( entityIndex != LAST_CHANGED ) { if (propIndex == DSPropertyAI_INSTANCE) { // lookup stats for the entity in the mirror sint32 aiInstance= CMirrorPropValueRO( TheDataset, entityIndex, DSPropertyAI_INSTANCE ); sint32 x= CMirrorPropValueRO( TheDataset, entityIndex, DSPropertyPOSX ); sint32 y= CMirrorPropValueRO( TheDataset, entityIndex, DSPropertyPOSY ); CMirrorPropValueRO whoSeesMe ( TheDataset, entityIndex, DSPropertyWHO_SEES_ME ); R2_VISION::TInvisibilityLevel invisibility= (R2_VISION::TInvisibilityLevel)(whoSeesMe&((1<MoveChecker->teleport(entityIndex, x, y, CTickEventHandler::getGameCycle()); } // check that the character being teleported exists in the r2VisionUniverse and get hold of their old AIInstance const R2_VISION::SUniverseEntity* theEntity= pCGPMS->RingVisionUniverse->getEntity(entityIndex); BOMB_IF(theEntity==NULL, "Failed to identify the character that the mirror tells us is being removed", TheDataset.getNextChangedValue( entityIndex, propIndex ); continue); sint32 oldAiInstance= theEntity->AIInstance; // check whether this is a real teleportation or just a move if (oldAiInstance==aiInstance) { // the aiInstance hasn't changed so just perform a move // note: this happens systematicaly on appearance of a new entity - the ai instance is // setup once via code that manages appearance of new entities in mirror and it's setup // again here... the coordinates are probably the same too but since I can't guarantee // it I prefer to let the code do its stuff pCGPMS->RingVisionUniverse->setEntityPosition(entityIndex,x,y); } else { // make sure the instance we're teleporting to exists pCGPMS->_checkAddCharacterToRingAIInstance(aiInstance); // teleport entity within the ring vision universe pCGPMS->RingVisionUniverse->teleportEntity(entityIndex,aiInstance,x,y,invisibility); // if this was the last entity in their old instance then get rid of the instance pCGPMS->_checkRemoveCharacterFromRingAIInstance(oldAiInstance); } } else if (propIndex == DSPropertyPOSX) { // lookup stats for the entity in the mirror CMirrorPropValueRO x ( TheDataset, entityIndex, DSPropertyPOSX ); CMirrorPropValueRO y ( TheDataset, entityIndex, DSPropertyPOSY ); // update the cell CMirrorPropValue1DS cell ( TheDataset, entityIndex, DSPropertyCELL ); uint32 cx = (uint16) ( + x()/CWorldPositionManager::getCellSize() ); uint32 cy = (uint16) ( - y()/CWorldPositionManager::getCellSize() ); cell = (cx<<16) + cy; // move entity within the ring vision universe pCGPMS->RingVisionUniverse->setEntityPosition(entityIndex,x,y); } else if (propIndex == DSPropertyWHO_SEES_ME) { // lookup stats for the entity in the mirror CMirrorPropValueRO whoSeesMe ( TheDataset, entityIndex, DSPropertyWHO_SEES_ME ); uint32 visibilityValue= ((uint32)whoSeesMe==0)? R2_VISION::WHOSEESME_INVISIBLE_DM: ((uint32)(whoSeesMe+1)==0)? R2_VISION::WHOSEESME_VISIBLE_MOB: (uint32)whoSeesMe; // apply the change to the entity pCGPMS->RingVisionUniverse->setEntityInvisibilityInfo(entityIndex, visibilityValue); } TheDataset.getNextChangedValue( entityIndex, propIndex ); } TheDataset.endChangedValues(); } void CGlobalPositionManagerService::processMirrorUpdates() { // Process entities added to mirror TheDataset.beginAddedEntities(); TDataSetRow entityIndex = TheDataset.getNextAddedEntity(); while ( entityIndex != LAST_CHANGED ) { //nldebug( "%u: OnAddEntity %d", CTickEventHandler::getGameCycle(), entityIndex ); CWorldPositionManager::onAddEntity( entityIndex ); entityIndex = TheDataset.getNextAddedEntity(); } TheDataset.endAddedEntities(); // Process entities removed from mirror TheDataset.beginRemovedEntities(); CEntityId *id; entityIndex = TheDataset.getNextRemovedEntity( &id ); while ( entityIndex != LAST_CHANGED ) { //nldebug( "%u: OnRemoveEntity %d", CTickEventHandler::getGameCycle(), entityIndex ); CWorldPositionManager::onRemoveEntity(entityIndex); entityIndex = TheDataset.getNextRemovedEntity( &id ); } TheDataset.endRemovedEntities(); // Process properties changed and notified in the mirror TPropertyIndex propIndex; TheDataset.beginChangedValues(); TheDataset.getNextChangedValue( entityIndex, propIndex ); while ( entityIndex != LAST_CHANGED ) { //nldebug( "%u: OnPosChange %d", CTickEventHandler::getGameCycle(), entityIndex ); //nlassert( propIndex == DSPropertyPOSX ); // (X, Y, Z, Theta) use "group notification" with prop X (and are the only ones with notification) if (propIndex == DSPropertyPOSX) { // TEMP: while we don't handle all by entity indices, we need to test if the entityId has been notified (added) CWorldEntity *entity = CWorldPositionManager::getEntityPtr(entityIndex); if (entity) { if (entity->getType() == CWorldEntity::Player && entity->CheckMotion) { // this is a player and has to check motion // then reset the player position according to the mirror pos H_AUTO(gpmsUpdateResetServerPosition); CWorldPositionManager::resetPrimitive(entity); entity->PosInitialised = true; } else { H_AUTO(gpmsUpdateServerPosition); //nlinfo("Update %s pos: %d,%d,%d - %d", entity->Id.toString().c_str(), entity->X(), entity->Y(), entity->Z(), entity->X.getWriterServiceId()); CWorldPositionManager::updateEntityPosition(entity); } } } //nldebug( "Pos changed from mirror E%d", entityIndex ); TheDataset.getNextChangedValue( entityIndex, propIndex ); } TheDataset.endChangedValues(); } /****************************************************************\ gpmsUpdate() \****************************************************************/ void CGlobalPositionManagerService::gpmsUpdate() { if ( ! pCGPMS->Mirror.mirrorIsReady() ) return; H_AUTO(gpmsUpdate); // process vision update for non-ring shards if (!IsRingShard) { // also update internal clock (increase tick counter by one) H_TIME(PositionManagerUpdate, CWorldPositionManager::update();); uint i; for (i=0; iTracked.size(); ++i) CWorldPositionManager::displayEntity(CWorldPositionManager::getEntityIndex(pCGPMS->Tracked[i])); } // process vision update for ring shards if (IsRingShard) { H_AUTO(ringVisionUpdate); pCGPMS->RingVisionUniverse->update(); pCGPMS->RingVisionDeltaManager->update(); } } // gpmsUpdate // /****************************************************************\ release() \****************************************************************/ void CGlobalPositionManagerService::release() { CMessages::release(); if (!IsRingShard) { CWorldPositionManager::release(); } CSheets::release(); }// release // NLNET_SERVICE_MAIN( CGlobalPositionManagerService, "GPMS", "gpm_service", 0, CbArray, "", "" );