// 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 "stdpch.h" #include "mirrors.h" #include "game_share/tick_event_handler.h" #include "nel/net/service.h" #include "game_share/misc_const.h" #include "game_share/fame.h" #include "ai_bot_npc.h" #include "ai_grp_npc.h" #include "server_share/r2_variables.h" #include "server_share/r2_vision.h" #include "ai.h" #include "ai_player.h" using namespace std; using namespace NLMISC; using namespace NLNET; using namespace MBEHAV; CMirror CMirrors::Mirror; CMirroredDataSet *CMirrors::DataSet = NULL; CMirroredDataSet *CMirrors::FameDataSet = NULL; const uint MAX_NB_ENTITIES_ISOLATED = 25000; CEntityId *IsolatedEntityId = NULL; sint32 *IsolatedX = NULL; sint32 *IsolatedY = NULL; sint32 *IsolatedZ = NULL; float *IsolatedTheta = NULL; uint32 *IsolatedSheet = NULL; uint32 *IsolatedSheetServer = NULL; TDataSetRow *IsolatedTarget = NULL; // sint32 uint32 *IsolatedMode = NULL; // only mode enum uint32 *IsolatedBehaviour = NULL; uint32 *IsolatedCurrentHitPoints = NULL; uint32 *IsolatedMaxHitPoints = NULL; uint32 *IsolatedBestRoleLevel = NULL; uint8 *IsolatedCombatState = NULL; extern CVariable RingMaxSelectDist; /* * Initialisation 2 */ void cbMirrorIsReady( CMirror *mirror ) { CMirrors::initMirror(); } void cbMirrorReadyForAddEntity( CMirror *mirror ) { // Init fx manager const TDeclaredEntityRangeOfType& declERT = TheDataset.getDeclaredEntityRanges(); { TDeclaredEntityRangeOfType::const_iterator it = declERT.find( RYZOMID::fx_entity ); const TDeclaredEntityRange& range = GET_ENTITY_TYPE_RANGE(it); CFxEntityManager::getInstance()->init( range.baseIndex(), range.size() ); } } #define initIsolatedPropTable( name, thetype, defaultvalue ) \ name = new thetype [MAX_NB_ENTITIES_ISOLATED]; \ for ( i=0; i!=MAX_NB_ENTITIES_ISOLATED; ++i ) \ name[i] = defaultvalue /* * Initialisation 1 */ void CMirrors::init( void (*cbUpdate)(), void (*cbSync)(), void (*cbRelease)() ) { // Init the mirror system vector datasetNames; datasetNames.push_back( "fe_temp" ); datasetNames.push_back( "fame" ); Mirror.init( datasetNames, cbMirrorIsReady, cbUpdate, cbSync, cbRelease, AISTag ); Mirror.addCallbackWhenMirrorReadyForUse( cbMirrorReadyForAddEntity ); } /* * Init after the mirror init */ void CMirrors::initMirror() { //Mirror.declareEntityTypeUser( RYZOMID::player ); Mirror.declareEntityTypeOwner( RYZOMID::npc, TotalMaxNpc.get() ); Mirror.declareEntityTypeOwner( RYZOMID::creature, TotalMaxFauna.get()+TotalMaxPet.get() ); Mirror.declareEntityTypeOwner( RYZOMID::fx_entity, TotalMaxFx ); DataSet = &(Mirror.getDataSet("fe_temp")); DataSet->declareProperty( "AIInstance", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "X", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "Y", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "Z", PSOReadWrite ); DataSet->declareProperty( "Theta", PSOReadWrite ); DataSet->declareProperty( "Sheet", PSOReadWrite ); DataSet->declareProperty( "SheetServer", PSOReadWrite ); DataSet->declareProperty( "NPCAlias", PSOWriteOnly ); DataSet->declareProperty( "Mode", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "Behaviour", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "Target", PSOReadWrite | PSONotifyChanges ); DataSet->declareProperty( "CurrentHitPoints", PSOReadOnly ); DataSet->declareProperty( "CurrentRunSpeed", PSOReadOnly ); DataSet->declareProperty( "CurrentWalkSpeed", PSOReadOnly ); DataSet->declareProperty( "MaxHitPoints", PSOReadOnly ); DataSet->declareProperty( "BestRoleLevel", PSOReadOnly ); DataSet->declareProperty( "CombatState", PSOReadOnly ); DataSet->declareProperty( "TeamId", PSOReadOnly | PSONotifyChanges ); DataSet->declareProperty( "InOutpostZoneAlias", PSOReadWrite ); DataSet->declareProperty( "InOutpostZoneSide", PSOReadWrite ); DataSet->declareProperty( "VisualPropertyA", PSOReadWrite ); DataSet->declareProperty( "VisualPropertyB", PSOReadWrite ); DataSet->declareProperty( "VisualPropertyC", PSOReadWrite ); DataSet->declareProperty( "ActionFlags", PSOReadWrite ); DataSet->declareProperty( "VisionCounter", PSOReadOnly ); DataSet->declareProperty( "Fuel", PSOReadOnly ); DataSet->declareProperty( "WhoSeesMe", PSOReadWrite ); DataSet->declareProperty( "ContextualProperty", PSOReadWrite ); initRyzomVisualPropertyIndices( *DataSet ); // init fame dataset FameDataSet = &(Mirror.getDataSet("fame")); CFameInterface::getInstance().setFameDataSet(FameDataSet); Mirror.setNotificationCallback( CMirrors::processMirrorUpdates ); } // a big bad global var ! extern CAIEntityPhysical *TempSpeaker; extern CBotPlayer *TempPlayer; void CMirrors::processMirrorUpdates() { TDataSetRow entityIndex; CEntityId *pEntityId; CFameInterface &fi = CFameInterface::getInstance(); // Process added entities DataSet->beginAddedEntities(); while ((entityIndex = DataSet->getNextAddedEntity()) != LAST_CHANGED) { const CEntityId& entityId = DataSet->getEntityId(entityIndex); // Create CAIPlayer object and add to maps if (entityId.getType() != RYZOMID::player) continue; { CMirrorPropValue _instanceNumber(*DataSet, entityIndex, DSPropertyAI_INSTANCE); const uint32 askedInstance=_instanceNumber(); CAIInstance *const aii = CAIS::instance().getAIInstance(askedInstance); if (!aii) { // The player is not in an AIInstance of that ais. // Floods on player connections, so no warning // nlwarning("AIInstance %d not found to spawn player %s", askedInstance, entityId.toString().c_str()); // FOREACH(it,CCont, CAIS::instance().AIList()) // { // nlwarning("exist AIInstance %d at index %d", (*it)->getInstanceNumber(), (*it)->getIndex()); // } continue; } // store the player in the correct instance. aii->getPlayerMgr()->addSpawnedPlayer(entityIndex, entityId); } } DataSet->endAddedEntities(); FameDataSet->beginAddedEntities(); while( (entityIndex = FameDataSet->getNextAddedEntity()) != LAST_CHANGED ) { fi.createFameOwner(*FameDataSet, entityIndex); } FameDataSet->endAddedEntities(); // Process removed entities DataSet->beginRemovedEntities(); while( (entityIndex = DataSet->getNextRemovedEntity(&pEntityId)) != LAST_CHANGED ) { CAIEntityPhysical *entityPhysPtr = CAIS::instance().getEntityPhysical(entityIndex); // just to check if its a player .. and remove it from Manager and Maps .. if ( entityPhysPtr && entityPhysPtr->getRyzomType()==RYZOMID::player) { CBotPlayer *botPlayer=NLMISC::safe_cast(entityPhysPtr); botPlayer->getOwner()->removeDespawnedPlayer(entityIndex); } } DataSet->endRemovedEntities(); FameDataSet->beginRemovedEntities(); while( (entityIndex = FameDataSet->getNextRemovedEntity(&pEntityId)) != LAST_CHANGED ) { fi.removeFameOwner(*FameDataSet, entityIndex); } FameDataSet->endRemovedEntities(); // Process properties changed and notified in the mirror TPropertyIndex propIndex; DataSet->beginChangedValues(); DataSet->getNextChangedValue( entityIndex, propIndex ); while (entityIndex != LAST_CHANGED) { if (propIndex == DSPropertyPOSX || propIndex == DSPropertyPOSX ) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { CBotPlayer *player=NLMISC::safe_cast(entityPhys); if (player) { CAIInstance* aiInstance = player->getAIInstance(); aiInstance->updateZoneTrigger(player); } } } } else if (propIndex == DSPropertyTEAM_ID) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { CBotPlayer *player=NLMISC::safe_cast(entityPhys); if (player) player->getOwner()->updatePlayerTeam(entityIndex); } } } else if (propIndex == DSPropertyAI_INSTANCE) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { // this player is already in a manager, despawn it CBotPlayer *const botPlayer=NLMISC::safe_cast(entityPhys); botPlayer->getOwner()->removeDespawnedPlayer(entityIndex); } // could be done with mirrored values. (!?). CMirrorPropValue _instanceNumber(*DataSet, entityIndex, DSPropertyAI_INSTANCE); const uint32 askedInstance=_instanceNumber(); CAIInstance *aii = CAIS::instance().getAIInstance(askedInstance); if (aii!=NULL) { // store the player in the correct instance. aii->getPlayerMgr()->addSpawnedPlayer(entityIndex, entityId); } else { if (askedInstance!=~0) { // no need to warn for ai number instance not in this AIS ! // nlwarning("AIInstance %u not found on AIInstance changement for player %s", askedInstance, entityId.toString().c_str()); // FOREACH(it,CCont, CAIS::instance().AIList()) // { // nlwarning("exist AIInstance %d at index %d", (*it)->getInstanceNumber(), (*it)->getIndex()); // } } } } } else if (propIndex == DSPropertyTARGET_ID) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CBotPlayer *player=NLMISC::safe_cast(entityPhys); // check if target is a local npc bot then handle bot targeted event CMirrorPropValueRO tgt(TheDataset, entityIndex, DSPropertyTARGET_ID); CAIEntityPhysical *target = CAIS::instance().getEntityPhysical(tgt()); // update the targering list if (entityIndex==TDataSetRow()) // no target. { player->setTarget(NULL); } else { if (player->isAggressive()) { CAIEntityPhysical *oldTarget=player->getTarget(); if ( !oldTarget || oldTarget!=target ) player->setTarget(target); } else { CAIEntityPhysical *oldTarget=player->getVisualTarget(); if ( !oldTarget || oldTarget!=target ) player->setVisualTarget(target); } } CSpawnBotNpc *bnpc = dynamic_cast(target); if (bnpc) { TempSpeaker = bnpc; TempPlayer = player; CGroupNpc &grpNpc = bnpc->getPersistent().grp(); if (IsRingShard) { CAIPos playerPos(player->pos()); CAIPos npcPos(bnpc->pos()); double dist = npcPos.quickDistTo(playerPos); bool dm = false; uint64 whoSeesMe = CMirrors::whoSeesMe(player->dataSetRow()); if (!R2_VISION::isEntityVisibleToPlayers(whoSeesMe)) { // The player is invisible, its a Dm / Gm: no zone event must be triggered dm = true; } if (!dm && dist <= RingMaxSelectDist) { grpNpc.processStateEvent(grpNpc.getEventContainer().EventPlayerTargetNpc); } } else { // generate en event on this bot group grpNpc.processStateEvent(grpNpc.getEventContainer().EventPlayerTargetNpc); } // grpNpc.processStateEvent(grpNpc.getEventContainer().EventPlayerTargetNpc); // grpNpc.getEventContainer().EventPlayerTargetNpc.processStateEvent(&grpNpc); // if player is in follow mode, then generate an suplementary event if (player->getFollowMode()) grpNpc.processStateEvent(grpNpc.getEventContainer().EventPlayerFollowNpc); // grpNpc.getEventContainer().EventPlayerFollowNpc.processStateEvent(&grpNpc); TempPlayer = NULL; TempSpeaker = NULL; } } } } else if (propIndex == DSPropertyMODE) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CBotPlayer *player=NLMISC::safe_cast(entityPhys); // if the player have a target, move it to the correct target list. if (player->isAggressive()) { CAIEntityPhysical *target=player->getVisualTarget(); if (target) player->setTarget(target); } else { CAIEntityPhysical *target=player->getTarget(); if (target) player->setVisualTarget(target); } } } } /* else if (propIndex == DSPropertyBEHAVIOUR) { CAIEntityPhysical *entityPhys=CAIS::instance().getEntityPhysical(entityIndex); if (entityPhys) { const CEntityId &entityId=DataSet->getEntityId(entityIndex); if (entityId.getType() == RYZOMID::player) { CBotPlayer *player=NLMISC::safe_cast(entityPhys); MBEHAV::EBehaviour const behaviour = player->getBehaviour(); if( behaviour == MBEHAV::CAST_OFF_SUCCESS ); { CAIEntityPhysical *target=player->getVisualTarget(); if (target) player->setTarget(target); } } } } */ DataSet->getNextChangedValue( entityIndex, propIndex ); } DataSet->endChangedValues(); } void CMirrors::release() { Mirror.release(); } bool CMirrors::mirrorIsReady() { return Mirror.mirrorIsReady(); } //--------------------------------------------------------------------------------- // Methods for retrieving data from mirrors TDataSetRow CMirrors::createEntity( CEntityId& entityId ) { // in ais, we always use entityId auto asigment by mirror if (Mirror.createEntity( entityId , true)) return DataSet->getDataSetRow( entityId ); else return TDataSetRow(); } void CMirrors::declareEntity( const TDataSetRow& entityIndex ) { DataSet->declareEntity( entityIndex ); // only in the main dataset } void CMirrors::removeEntity( const CEntityId& entityId ) { Mirror.removeEntity( entityId ); } bool CMirrors::exists( const TDataSetRow& entityIndex ) { /// is that a bug?? return DataSet->getEntityId( entityIndex ).asUint64() == 0; //return ! ((uint64)(DataSet->getEntityId( entityIndex )) != 0); } const NLMISC::CEntityId& CMirrors::getEntityId( const TDataSetRow& entityIndex ) { return DataSet->getEntityId( entityIndex ); } TDataSetRow CMirrors::getDataSetRow( const NLMISC::CEntityId& entityId ) { return DataSet->getDataSetRow( entityId ); } const uint16 CMirrors::getTeamId(const TDataSetRow& entityIndex) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyTEAM_ID ); // if ( value()==0 ) // return CTEAM::InvalidTeamId; return value; } CAICoord CMirrors::x( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyPOSX ); if ( value()==0 ) return CAICoord(); return CAICoord((double)value()/(double)1000); } CAICoord CMirrors::y( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyPOSY ); if ( value()==0 ) return CAICoord(); return CAICoord((double)value()/(double)1000); } sint32 CMirrors::z( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyPOSZ ); return value(); } float CMirrors::theta( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyORIENTATION ); return value(); } void CMirrors::setPosAndTheta( const TDataSetRow& entityIndex, sint32 posX, sint32 posY, sint32 posZ, float angleRad ) { #ifdef NL_DEBUG //nldebug( "%d: E%d: Setting pos to %d %d", CTickEventHandler::getGameCycle(), entityIndex, posX, posY ); /*if ( posX == 0 ) nldebug( "E%d: X is zero", entityIndex ); if ( posY == 0 ) nldebug( "E%d: Y is zero", entityIndex );*/ #endif CMirrorPropValue valueX( *DataSet, entityIndex, DSPropertyPOSX ); CMirrorPropValue valueY( *DataSet, entityIndex, DSPropertyPOSY ); CMirrorPropValue valueZ( *DataSet, entityIndex, DSPropertyPOSZ ); CMirrorPropValue valueT( *DataSet, entityIndex, DSPropertyORIENTATION ); valueX = posX; valueY = posY; valueZ = posZ; valueT = angleRad; //nldebug( "%u: Pos&theta of E%d set", CTickEventHandler::getGameCycle(), entityIndex ); } void CMirrors::setTheta( const TDataSetRow& entityIndex, float angleRad ) { CMirrorPropValue valueT( *DataSet, entityIndex, DSPropertyORIENTATION ); valueT = angleRad; //nldebug( "%u: Theta of E%d set", CTickEventHandler::getGameCycle(), entityIndex ); } NLMISC::CSheetId CMirrors::sheet( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertySHEET ); return NLMISC::CSheetId(value()); } void CMirrors::initSheet( const TDataSetRow& entityIndex, const NLMISC::CSheetId& sheetId ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertySHEET ); value = sheetId.asInt(); } NLMISC::CSheetId CMirrors::sheetServer( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertySHEET_SERVER ); return NLMISC::CSheetId(value()); } void CMirrors::initSheetServer( const TDataSetRow& entityIndex, const NLMISC::CSheetId& sheetId ) { nlassert(sheetId != NLMISC::CSheetId::Unknown); CMirrorPropValue value( *DataSet, entityIndex, DSPropertySHEET_SERVER ); value = sheetId.asInt(); } void CMirrors::initNPCAlias( const TDataSetRow& entityIndex, TAIAlias alias ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyNPC_ALIAS ); value = alias; } TDataSetRow CMirrors::target( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyTARGET_ID ); return value(); } void CMirrors::setVPA( const TDataSetRow& entityIndex, const SAltLookProp &prop ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyVPA ); value = prop; } void CMirrors::setVisualPropertyA( const TDataSetRow& entityIndex, const SPropVisualA &prop ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyVPA ); value = prop; } void CMirrors::setVisualPropertyB( const TDataSetRow& entityIndex, const SPropVisualB &prop ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyVPB ); value = prop; } void CMirrors::setVisualPropertyC( const TDataSetRow& entityIndex, const SPropVisualC &prop ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyVPC ); value = prop; } void CMirrors::setBehaviour( const TDataSetRow& entityIndex, MBEHAV::EBehaviour b ) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyBEHAVIOUR ); value = b; } uint32 CMirrors::bestRoleLevel( const TDataSetRow& entityIndex ) { CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyBEST_ROLE_LEVEL ); return value(); } uint64 CMirrors::whoSeesMe( const TDataSetRow& entityIndex ) { BOMB_IF( !DataSet->isAccessible(entityIndex), "Try to access to the WhoSeesMe property in the mirror but with an invalid ", return 0L); CMirrorPropValueRO value( *DataSet, entityIndex, DSPropertyWHO_SEES_ME ); return value(); } /* * Set target (row) */ void CMirrors::setTarget(const TDataSetRow& entityIndex, const TDataSetRow& target) { CMirrorPropValue value( *DataSet, entityIndex, DSPropertyTARGET_ID ); value = target; } /// :KLUDGE: This method implementation should be in game_share instead of AIS and EGS. Both me and the other one are lazy bastards. void MBEHAV::TMode::setModeAndPos( EMode mode, const TDataSetRow& entityIndex ) { Mode = mode; CMirrorPropValueRO propX( TheDataset, entityIndex, DSPropertyPOSX ); Pos.X16 = (uint16)(propX() >> 4); CMirrorPropValueRO propY( TheDataset, entityIndex, DSPropertyPOSY ); Pos.Y16 = (uint16)(propY() >> 4); //nldebug( "Setting MODE %s for E%u with current pos %d, %d", modeToString( mode ).c_str(), entityIndex.getIndex(), propX(), propY() ); } #include "event_reaction_include.h"