// 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 files #include "stdpch.h" #include "npc_description_msg.h" #include "ai.h" #include "ai_mgr_pet.h" #include "ai_share/ai_actions.h" #include "aids_interface.h" #include "ai_player.h" #include "ai_grp_pet.h" #include "ai_bot_npc.h" #include "ai_mgr_npc.h" #include "ai_grp_npc.h" #include "ai_bot_fauna.h" #include "ai_mgr_fauna.h" #include "ai_grp_fauna.h" #include "ai_profile_fauna.h" // for CCorpseFaunaProfile #include "dyn_mission.h" #include "mirrors.h" #include "game_share/tick_event_handler.h" #include "messages.h" #include "server_share/msg_brick_service.h" #include "server_share/msg_ai_service.h" #include "server_share/effect_message.h" // ai_share #include "ai_share/ai_actions_dr.h" #include "egs_interface.h" #include "dyn_grp_inline.h" using namespace NLMISC; using namespace NLNET; using namespace AITYPES; //-------------------------------------------------------------------------- // CTransportClass message receivers //-------------------------------------------------------------------------- class CCharacterBotChatBeginEndReceiver: public CCharacterBotChatBeginEnd { virtual void callback (const std::string &name, NLNET::TServiceId id) { // make sure bot chat start vector size is even if (BotChatStart.size()&1) { nlwarning("CCharacterBotChatBeginEndReceiver::callback(): Invalid BotChatStart vector length"); return; } // make sure bot chat end vector size is even if (BotChatEnd.size()&1) { nlwarning("CCharacterBotChatBeginEndReceiver::callback(): Invalid BotChatEnd vector length"); return; } // nlwarning("Cannot initiate Chat between Bot, will be correct in next AI integration"); // start new bot chats { for (uint i=0;i(botId)); CUnifiedNetwork::getInstance()->send(serviceId, msgout); } void CMessages::notifyBotDeath(NLNET::TServiceId serviceId, uint32 botAlias, const NLMISC::CEntityId& botId) { CMessage msgout("BOT_DEATH_NOTIFICATION"); msgout.serial(botAlias); msgout.serial(const_cast(botId)); CUnifiedNetwork::getInstance()->send(serviceId, msgout); } void CMessages::notifyBotStopNpcControl(NLNET::TServiceId serviceId, uint32 botAlias, const NLMISC::CEntityId& botId) { CMessage msgout("BOT_STOPCCONTROL_NOTIFICATION"); msgout.serial(botAlias); msgout.serial(const_cast(botId)); CUnifiedNetwork::getInstance()->send(serviceId, msgout); } //-------------------------------------------------------------------------- // Messages from the DSS //-------------------------------------------------------------------------- #include "nel/ligo/primitive.h" #include "nel/ligo/primitive_utils.h" #include "ai_share/ai_share.h" #include "ai_share/ai_actions.h" #include "game_share/task_list.h" // Load Scenarip phase 1 (remove old entities) class CTaskRingGoLive1 : public CTask { public: CTaskRingGoLive1(uint32 aiInstance, bool isBase) :_AiInstance(aiInstance), _IsBase(isBase){} virtual void doOperation() { nldebug("Tick %u CTaskRingGoLive1 (Despawn) %u %u", getTime(), _AiInstance, _IsBase); std::string actFilename = toString("r2.%04d.act.primitive", _AiInstance); std::string baseFilename= toString("r2.%04d.base.primitive", _AiInstance); ICommand::execute(toString("createDynamicAIInstance %d", _AiInstance), *InfoLog); ICommand::execute(toString("unloadPrimitiveFile \"%s\"", actFilename.c_str()), *InfoLog); if (_IsBase) { ICommand::execute(toString("unloadPrimitiveFile \"%s\"", baseFilename.c_str()), *InfoLog); } } private: uint32 _AiInstance; bool _IsBase; }; // Load Scenarip phase 2 (insert new entities) class CTaskRingGoLive2 : public CTask { public: CTaskRingGoLive2(TSessionId sessionId, uint32 aiInstance, bool isBase) :_SessionId(sessionId), _AiInstance(aiInstance), _IsBase(isBase){} virtual void doOperation() { nldebug("Tick %u, CTaskRingGoLive2 (Spawn) session %u instance %u %u", getTime(), _SessionId.asInt(), _AiInstance, _IsBase); CAIActions::IExecutor* executer; executer = CAIActions::getExecuter(); ICommand::execute(toString("createDynamicAIInstance %d", _AiInstance), *InfoLog); AI_SHARE::CAIActionsDataRecordPtr dataRec; dataRec.init(&Pdr); dataRec.applyToExecutor(*executer); { std::string cmd = toString("spawnManagers r2.%04d.%s", _SessionId.asInt(), _IsBase?"base":"act"); ICommand::execute(cmd, *InfoLog); } { std::string cmd = toString("spawnManagers r2.%04d.%s", _SessionId.asInt(), _IsBase?"base.zone_trigger":"act.zone_trigger"); ICommand::execute(cmd, *InfoLog); } } public: CPersistentDataRecord Pdr; private: uint32 _AiInstance; TSessionId _SessionId; bool _IsBase; }; static void cbR2GoLive( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { TSessionId sessionId; uint32 aiInstance; bool isBase; uint32 size; msgin.serial(sessionId); msgin.serial(aiInstance); msgin.serial(isBase); msgin.serial(size); if (size != 0) { // case where we want to start an instance but (at start of an edition session) but animation not started uint32 tick = CTimeInterface::gameCycle(); CTaskRingGoLive1* task1 = new CTaskRingGoLive1( aiInstance, isBase); CTaskRingGoLive2* task2 = new CTaskRingGoLive2( sessionId, aiInstance, isBase); task2->Pdr.clear(); uint8* buffer = new uint8[size]; msgin.serialBuffer(buffer, size); task2->Pdr.fromBuffer((const char*)buffer, size); delete [] buffer; CAIS::instance().addTickedTask(tick + 1,task1); CAIS::instance().addTickedTask(tick + 2, task2); } if( isBase ) { // ack to unblock DSS session queue CMessage msgout("SESSION_ACK"); msgout.fastWrite( aiInstance ); CUnifiedNetwork::getInstance()->send("DSS",msgout); nlinfo( "R2An: ack sent to DSS for anim session %u", aiInstance ); } return; } class CTaskR2StopLive : public CTask { public: CTaskR2StopLive(uint32 aiInstance, bool isBase) :_AiInstance(aiInstance), _IsBase(isBase){} virtual void doOperation() { ICommand::execute(toString("createDynamicAIInstance %d", _AiInstance), *InfoLog); // load a primitiv.binprim from DSS //ICommand::execute(toString("despawnManagers %s", isBase?"base":"act"), *InfoLog); ICommand::execute(toString("unloadPrimitiveFile \"r2.%04d.act.primitive\"", _AiInstance), *InfoLog); //isBase == false <=> stop act // isBase == true <=> stop Live if (_IsBase) { ICommand::execute(toString("unloadPrimitiveFile \"r2.%04d.base.primitive\"", _AiInstance), *InfoLog); CAIS::instance().destroyAIInstance(_AiInstance, true); } } private: uint32 _AiInstance; bool _IsBase; }; static void cbR2StopLive( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { bool isBase; uint32 aiInstance; msgin.serial(aiInstance); msgin.serial(isBase); uint32 tick = CTimeInterface::gameCycle(); CTask* task1 = new CTaskR2StopLive( aiInstance, isBase); CAIS::instance().addTickedTask(tick ,task1); } class CTaskR2StartInstance: public CTask { public: CTaskR2StartInstance(uint32 aiInstance) :_AiInstance(aiInstance){} virtual void doOperation() { // destroy the instance before exist CAIS::instance().destroyAIInstance(_AiInstance, false); ICommand::execute(toString("createDynamicAIInstance %d", _AiInstance), *InfoLog); } private: uint32 _AiInstance; }; static void cbR2StartInstance( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { uint32 aiInstance; msgin.serial(aiInstance); uint32 tick = CTimeInterface::gameCycle(); CTask* task1 = new CTaskR2StartInstance( aiInstance); CAIS::instance().addTickedTask(tick ,task1); } //-------------------------------------------------------------------------- // Messages from the GPMS //-------------------------------------------------------------------------- // static void cbAddEntities( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { // This is where we should deal with player record creation } //-------------------------------------------------------------------------- // Messages from the EGS //-------------------------------------------------------------------------- void cbValidateSourceSpawn( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { H_AUTO(ValidateSourceSpawn); // Read/write header CMessage msgout( "SRC_SPWN_VLD_R" ); uint32 nbSources; //CVector prospectingPos; //msgin.fastRead( prospectingPos ); // Get world position of prospector const RYAI_MAP_CRUNCH::CWorldMap& worldMap = CWorldContainer::getWorldMap(); //RYAI_MAP_CRUNCH::CWorldPosition prospectorWorldPos; //CAIVector prospectingAiPos( (double)prospectingPos.x, (double)prospectingPos.y ); //bool isProspectingPosValid = worldMap.setWorldPosition( (double)prospectingPos.z, prospectorWorldPos, prospectingAiPos ); msgin.fastRead( nbSources ); msgout.fastWrite( nbSources ); for ( uint i=0; i!=nbSources; ++i ) { // Read request CVector2f pos2f; TDataSetRow sourceDataSetRow; msgin.serial( pos2f ); msgin.serial( sourceDataSetRow ); msgout.serial( sourceDataSetRow ); // Check position RYAI_MAP_CRUNCH::CWorldPosition wPos; CAIVector aiPos( (double)pos2f.x, (double)pos2f.y ); std::vector wPosList; bool canSpawnHere = worldMap.setWorldPosition( AITYPES::vp_auto, wPos, aiPos ); //worldMap.getWorldPositions( wPosList, aiPos ); //for ( std::vector::const_iterator it=wPosList.begin(); it!=wPosList.end(); ++it ) { // Discard if the position is in water or in interiors if ( canSpawnHere && (wPos.getFlags() & (RYAI_MAP_CRUNCH::Water | RYAI_MAP_CRUNCH::Interior)) ) canSpawnHere = false; //continue; msgout.fastWrite( canSpawnHere ); // TODO: Browse the possible surfaces for the position (x,y), then keep only the one(s) // for which a pathfinding from the prospector to the source limited to 5 surfaces succeeds } } // Send reply sendMessageViaMirror( serviceId, msgout ); // via mirror because receiver will access mirror //nldebug( "Processed %u forage source pos validations", nbSources ); } void cbValidateSimpleSourceSpawn( NLNET::CMessage& msgin, const std::string &serviceName, NLNET::TServiceId serviceId ) { H_AUTO(ValidateSourceSpawn); // Read/write header CMessage msgout( "SRC_SPWN_VLD_R" ); // Read request uint32 nbSources = 1; msgout.fastWrite( nbSources ); CVector2f pos2f; TDataSetRow sourceDataSetRow; msgin.serial( pos2f ); msgin.serial( sourceDataSetRow ); msgout.serial( sourceDataSetRow ); // Check position const RYAI_MAP_CRUNCH::CWorldMap& worldMap = CWorldContainer::getWorldMap(); RYAI_MAP_CRUNCH::CWorldPosition wPos; CAIVector aiPos( (double)pos2f.x, (double)pos2f.y ); bool canSpawnHere = worldMap.setWorldPosition( AITYPES::vp_auto, wPos, aiPos ); // Discard if the position is in water or in interiors if ( canSpawnHere && (wPos.getFlags() & (RYAI_MAP_CRUNCH::Water | RYAI_MAP_CRUNCH::Interior)) ) canSpawnHere = false; msgout.fastWrite( canSpawnHere ); // Send reply sendMessageViaMirror( serviceId, msgout ); // via mirror because receiver will access mirror //nldebug( "Processed a simple forage source pos validation" ); } //-------------------------------------------------------------------------- // CTransportClass callbacks //-------------------------------------------------------------------------- /* void CMsgAIOpenMgrs::callback (const std::string &serviceName, uint8 sid) { if (Name.size()!=Type.size() || Name.size()!=MgrId.size()) { nlwarning("Size missmatch in vectors in CMsgAIOpenMgrs message - message ignored"); return; } // iterate through the entries in the message opening new managers for (uint i=0;iaddCallbackArray( CbArray, sizeof(CbArray)/sizeof(CbArray[0]) ); // hook up callback to service up of data service // CUnifiedNetwork::getInstance()->setServiceUpCallback( std::string("AIDS"), cbAIDSServiceUp, 0); } ////////////////////////////////////////////////////////////////////////// // CallBack part ////////////////////////////////////////////////////////////////////////// void CEnableAggroOnPlayerImp::callback (const std::string &name, NLNET::TServiceId id) { CAIEntityPhysical *phys=CAIS::instance().getEntityPhysical(EntityRowId); if (!phys) { // When go from test to edition the ai instance is destroyed if (!IsRingShard) { nlwarning("CEnableAggroOnPlayerImp::callback: unknow entity %s on this AIS !", EntityRowId.toString().c_str()); } return; } switch(phys->getRyzomType()) { case RYZOMID::player: { CBotPlayer *const bot=safe_cast(phys); bot->setAggroable(EnableAggro); if (!EnableAggro) bot->forgotAggroForAggroer(); } break; default: nlwarning("CEnableAggroOnPlayerImp::callback: non player entity ! %s(%u) on this AIS !", phys->getEntityId().toString().c_str(), EntityRowId.toString().c_str()); break; } } void CAIAskForInfosOnEntityImp::callback (const std::string &name, NLNET::TServiceId id) { CAIInfosOnEntityMsg msg; msg.AskerRowID=AskerRowID; msg.EntityRowId=EntityRowId; CArrayStringWriter stringWriter(msg.Infos); CAIEntityPhysical *phys=CAIS::instance().getEntityPhysical(EntityRowId); if (phys) { switch(phys->getRyzomType()) { case RYZOMID::creature: case RYZOMID::npc: case RYZOMID::pack_animal: { const CBot *const bot=safe_cast(&phys->getPersistent()); std::vector strings = bot->getMultiLineInfoString(); msg.Infos.insert(msg.Infos.end(), strings.begin(), strings.end()); } break; default: std::vector strings = phys->getMultiLineInfoString(); msg.Infos.insert(msg.Infos.end(), strings.begin(), strings.end()); break; } } else { nlwarning("CAIAskForInfosOnEntityImp::callback: entity %s is unknow on this AIS.", EntityRowId.toString().c_str()); stringWriter.append(toString("%s : unknow entity index %s", IService::getInstance()->getServiceShortName().c_str(), EntityRowId.toString().c_str())); } msg.send("EGS"); } void CChangeActionFlagMsgImp::callback (const std::string &name, NLNET::TServiceId id) { const uint32 size = (uint32)Entities.size(); nlassert( size == ActionFlags.size() && size == Values.size()); for (uint32 i = 0 ; i < size ; ++i) { CAIEntityPhysical *const phys=CAIS::instance().getEntityPhysical(Entities[i]); if (!phys) continue; if (Values[i]) phys->setActionFlags((RYZOMACTIONFLAGS::TActionFlag)ActionFlags[i]); else phys->removeActionFlags((RYZOMACTIONFLAGS::TActionFlag)ActionFlags[i]); } } void CChangeCreatureModeMsgImp::callback (const std::string &name, NLNET::TServiceId id) { CAIEntityPhysical *phys=CAIS::instance().getEntityPhysical(CreatureId); if (!phys) return; static_cast(phys)->setMode( MBEHAV::EMode(NewMode) ); } // ////---------------------------------------------------------------- //// EGS -> AIS ask infos on entity ////---------------------------------------------------------------- //class CAIAskForInfosOnEntityMsg : public NLNET::CTransportClass //{ //public: // TDataSetRow EntityRowId; // // virtual void description () // { // className ("CAIAskForInfosOnEntityMsg"); // property ("EntityRowId", PropDataSetRow, TDataSetRow(), EntityRowId); // } // virtual void callback (const std::string &name, uint8 id) {} //}; // ////---------------------------------------------------------------- //// AIS -> EGS returns infos on entity as a vector of strings ////---------------------------------------------------------------- //class CAIInfosOnEntityMsg : public NLNET::CTransportClass //{ //public: // TDataSetRow EntityRowId; // std::vector Infos; // // virtual void description () // { // className ("CAIInfosOnEntityMsg"); // property ("EntityRowId", PropDataSetRow, TDataSetRow(), EntityRowId); // propertyCont ("Infos", PropString, Infos); // } // virtual void callback (const std::string &name, uint8 id) {} //}; void CMsgAIUploadActions::callback(const std::string &serviceName, NLNET::TServiceId sid) { for(uint i=0;i args; if (Data.size()-ideleteMgr(MgrId[i]); } void CMsgAIBackupMgrs::callback (const std::string &serviceName, NLNET::TServiceId sid) { nlassert(false); // AIDataService. nlwarning("*** Backup message received from AIDS but not treated ***"); CMsgAIFeedback("*** Backup message received from AIDS but not treated ***").send("AIDS"); } //-------------------------------------------------------------------------- // CTransportClass stub callbacks for outgoing messages //-------------------------------------------------------------------------- void CMsgAIServiceUp::callback (const std::string &serviceName, NLNET::TServiceId sid) { } void CMsgAIManagerUp::callback (const std::string &serviceName, NLNET::TServiceId sid) { } void CMsgAIFeedback::callback (const std::string &serviceName, NLNET::TServiceId sid) { } void CBSAIDeathReport::callback(const std::string &name, NLNET::TServiceId id) { for (uint i=0; igetRyzomType()!=RYZOMID::player); #else if (phys->getRyzomType()==RYZOMID::player) { if (Zombies[i]) { nldebug("CBSAIDeathReport::callback: ZombyPlayer entity %s !!", Bots[i].toString().c_str()); } continue; } #endif static_cast(phys)->setMode(MBEHAV::DEATH); if (Zombies[i]) { if (dynamic_cast(phys)==NULL) { nldebug("CBSAIDeathReport::callback: ZombyCastError entity %s !!", Bots[i].toString().c_str()); } else { nldebug("CBSAIDeathReport::callback: ZombyDEATHSet entity %s !!", Bots[i].toString().c_str()); } } switch(phys->getRyzomType()) { case RYZOMID::npc: { const CSpawnBotNpc *const sb = static_cast(phys); CSpawnGroupNpc &spawnGrp = sb->spawnGrp(); const CMgrNpc &mgr = spawnGrp.getPersistent().mgr(); spawnGrp.addBotToDespawnAndRespawnTime (&(sb->getPersistent()), spawnGrp.getPersistent().despawnTime(), spawnGrp.getPersistent().respawnTime()); if (spawnGrp.getPersistent().getSquadLeader(false)==&sb->getPersistent()) spawnGrp.getPersistent().processStateEvent(mgr.EventSquadLeaderKilled); CBotNpc* bot = NLMISC::safe_cast(&(sb->getPersistent())); spawnGrp.botHaveDied (bot); bot->notifyBotDeath(); std::string eventBotKilled; if (spawnGrp.getProfileParameter("event_bot_killed", eventBotKilled) && !eventBotKilled.empty()) { nlinfo("NPC: TRIGGER EVENT BOT KILLED"); } spawnGrp.getPersistent().processStateEvent(mgr.EventBotKilled); std::string eventGroupKilled; if (!spawnGrp.isGroupAlive(0*1)) { if (spawnGrp.getProfileParameter("event_group_killed", eventGroupKilled) && !eventGroupKilled.empty()) { nlinfo("NPC: TRIGGER EVENT GROUP KILLED"); } spawnGrp.getPersistent().processStateEvent(mgr.EventGrpEliminated); } if (!eventBotKilled.empty() || !eventGroupKilled.empty()) { CSpawnBot *const spBot = static_cast(phys); // make a vector of aggroable player CBotAggroOwner::TBotAggroList const& aggroList = spBot->getBotAggroList(); //typedef std::map TBotAggroList; CBotAggroOwner::TBotAggroList::const_iterator aggroIt(aggroList.begin()), aggroEnd(aggroList.end()); TDataSetRow foundRow = TDataSetRow(); std::vector playerAggroable; for (; aggroIt != aggroEnd; ++aggroIt) { if (CMirrors::getEntityId(aggroIt->first).getType() != RYZOMID::player) continue; CAIEntityPhysical* ep = CAIS::instance().getEntityPhysical(aggroIt->first); if (!ep) continue; CBotPlayer const* const player = NLMISC::safe_cast(ep); if (!player) continue; if (!player->isAggroable()) continue; playerAggroable.push_back(aggroIt->first); } NLNET::CMessage msgout("TRIGGER_WEBIG"); if (!eventBotKilled.empty()) msgout.serial(eventBotKilled); else msgout.serial(eventGroupKilled); uint32 nbPlayers = (uint32)playerAggroable.size(); msgout.serial(nbPlayers); for (uint32 i = 0; i < nbPlayers; i++) { msgout.serial(playerAggroable[i]); } sendMessageViaMirror("EGS", msgout); } } break; case RYZOMID::creature: { CSpawnBotFauna *const sb = static_cast(phys); CSpawnGroupFauna &spawnGrp = sb->spawnGrp(); CMgrFauna &mgr = spawnGrp.getPersistent().mgr(); sb->setAIProfile(new CCorpseFaunaProfile(sb)); { const sint32 DespawnTime=spawnGrp.getPersistent().timer(CGrpFauna::CORPSE_TIME); const sint32 RespawnTime=spawnGrp.getPersistent().timer(CGrpFauna::RESPAWN_TIME); // set the _Bot to be despawn and gives the control to its group. // despawn the corpse after DespawnTime, and hence respawn after DespawnTime+RespawnTime since now. spawnGrp.addBotToDespawnAndRespawnTime(&sb->getPersistent(),(uint32) DespawnTime, (uint32) DespawnTime + (uint32) RespawnTime); } spawnGrp.getPersistent().processStateEvent(mgr.EventBotKilled); if (spawnGrp.isGroupAlive()) break; spawnGrp.getPersistent().processStateEvent(mgr.EventGrpEliminated); } break; default: break; } } } void CEGSExecutePhraseMsg::callback(const std::string &name, NLNET::TServiceId id) { } void CAITauntImp::callback (const std::string &name, NLNET::TServiceId id) { CAIEntityPhysical *entity=CAIS::instance().getEntityPhysical(TargetRowId); if (!entity) return; CBotAggroOwner *aggroOwner=NULL; switch (entity->getRyzomType()) { case RYZOMID::creature: case RYZOMID::npc: aggroOwner=NLMISC::safe_cast(entity); break; default: break; } if (!aggroOwner) return; aggroOwner->maximizeAggroFor(PlayerRowId); } void CAIPlayerRespawnMsgImp::callback (const std::string &name, NLNET::TServiceId id) { CAIEntityPhysical *entity=CAIS::instance().getEntityPhysical(PlayerRowId); if (!entity) return; CBotPlayer *player=NLMISC::safe_cast(entity); if (!player) return; player->forgotAggroForAggroer(); } void sAggroLost(TDataSetRow playerBot, TDataSetRow targetBot) { CAIEntityPhysical *entity=CAIS::instance().getEntityPhysical(playerBot); // necessary ? if ( !entity || CMirrors::getEntityId(playerBot).getType()!=RYZOMID::player) return; CAILostAggroMsg msg(targetBot, playerBot); msg.send("EGS"); CBotPlayer *player=NLMISC::safe_cast(entity); if (!player) return; player->removeAggroer(targetBot); } void sAggroGain(TDataSetRow playerBot, TDataSetRow targetBot) { CAIEntityPhysical *entity=CAIS::instance().getEntityPhysical(playerBot); // necessary ? if ( !entity || CMirrors::getEntityId(playerBot).getType()!=RYZOMID::player) return; CAIGainAggroMsg msg(targetBot, playerBot); msg.send("EGS"); CBotPlayer *player=NLMISC::safe_cast(entity); if (!player) return; player->addAggroer(targetBot); } void CSetBotHeadingImp::callback(const std::string &serviceName, NLNET::TServiceId serviceId) { CAIEntityPhysical *ep =CAIS::instance().getEntityPhysical(BotRowId); if (ep == NULL) { nlwarning("CSetBotHeadingImp : invalid bot id : %s (%u)", TheDataset.getEntityId(BotRowId).toString().c_str(), BotRowId.getIndex()); return; } // ok, we have an entity physical, try to make it a npc CSpawnBotNpc *sb = dynamic_cast(ep); if (sb == NULL) { nlwarning("CSetBotHeadingImp : bot id %s (%u) is not an NPC !", TheDataset.getEntityId(BotRowId).toString().c_str(), BotRowId.getIndex()); return; } // set the theta sb->setTheta(CAngle(double(Heading))); } void CWarnBadInstanceMsgImp::callback(const std::string &serviceName, NLNET::TServiceId serviceId) { CAIS::instance().warnBadInstanceMsgImp(serviceName, serviceId, *this); } #include "event_reaction_include.h"