// 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 "ai_aggro.h" #include "ai_entity_physical.h" #include "ai_entity_physical_inline.h" #include "ai_instance.h" #include "ai_player.h" ////////////////////////////////////////////////////////////////////////////// // CBotAggroEntry // ////////////////////////////////////////////////////////////////////////////// CBotAggroEntry::CBotAggroEntry(TDataSetRow const& bot, float aggro, CBotAggroOwner const& owner, NLMISC::CSmartPtr place) : _Bot(bot) , _Owner(owner) , _LastHitPlace(place) { #ifdef NL_DEBUG nlassert(aggro>=0.f); #endif _Aggro = aggro; _Owner.aggroGain(_Bot); } CBotAggroEntry::~CBotAggroEntry() { if(_Owner.getSendAggroLostToEGS()) _Owner.aggroLost(_Bot); } void CBotAggroEntry::operator =(CBotAggroEntry const& other) { #ifdef NL_DEBUG nlassert(&_Owner==&other._Owner); // same owner ? #endif _Bot = other._Bot; _Aggro = other._Aggro; } void CBotAggroEntry::addAggro(float aggro, NLMISC::CSmartPtr place) { // prevent to add aggro on a bot more than one time per tick. _Aggro += (1.f-_Aggro)*(aggro); if (place!=NULL) _LastHitPlace = place; } bool CBotAggroEntry::updateTime(uint32 const& ticks) const { const_cast(this)->decrementAggros(ticks); return _Aggro==0.f; } void CBotAggroEntry::setMinimum(float aggro, NLMISC::CSmartPtr place) { if (aggro>=0.f && aggro<=1.f && _Aggro=0.f && aggro<=1.f && _Aggro>aggro) { _Aggro = aggro; } } void CBotAggroEntry::decrementAggros(uint32 ticks) { static float const TOTAL_DECAY_TIME_SEC = 120.f; // temporary. static float const AGGRO_DEC = (1.f/(TOTAL_DECAY_TIME_SEC*10.f)); float const decay = (float)(ticks*AGGRO_DEC); if (_Aggro>decay) { _Aggro -= decay; } else { _Aggro = 0.f; } } ////////////////////////////////////////////////////////////////////////////// // CBotAggroOwner // ////////////////////////////////////////////////////////////////////////////// /* double CBotAggroOwner::defaultReturnDistCheck = 1.5; uint32 CBotAggroOwner::defaultD1Radius = 100 * 1000; // en mm uint32 CBotAggroOwner::defaultD2Radius = 50 * 1000; // en mm float CBotAggroOwner::defaultPrimaryGroupAggroCoef = 0.f; // %age, entre 0 et 1 float CBotAggroOwner::defaultSecondaryGroupAggroCoef = 0.f; // %age, entre 0 et 1 uint32 CBotAggroOwner::defaultAggroPropagationRadius = 60; // en m */ float CBotAggroOwner::getReturnDistCheck() const { return AggroReturnDistCheck; } float CBotAggroOwner::getD1Radius() const { return AggroD1Radius; } float CBotAggroOwner::getD2Radius() const { return AggroD2Radius; } float CBotAggroOwner::getPrimaryGroupAggroDist() const { return AggroPrimaryGroupDist; } float CBotAggroOwner::getPrimaryGroupAggroCoef() const { return AggroPrimaryGroupCoef; } float CBotAggroOwner::getSecondaryGroupAggroDist() const { return AggroSecondaryGroupDist; } float CBotAggroOwner::getSecondaryGroupAggroCoef() const { return AggroSecondaryGroupCoef; } float CBotAggroOwner::getAggroPropagationRadius() const { return AggroPropagationRadius; } CBotAggroOwner::CBotAggroOwner() : _LastHitTime(CTimeInterface::gameCycle()) , _AggroBlocked(false) , _ReturnAggroIgnored(false) , _DontAggro(false) , _SendAggroLostToEGS(true) , _ReturnPos() , _FirstHitPlace(NULL) //, _State(NoAggro) { } CBotAggroOwner::~CBotAggroOwner() { #ifdef NL_DEBUG nlassert(_BotAggroList.size()==0); // have to clear this vector before call destructor (coz of call back). #endif } void CBotAggroOwner::mergeAggroList(CBotAggroOwner const& otherList, float scale) { H_AUTO(AGGRO_mergeAggroList); NLMISC::clamp(scale, 0.0f, 1.0f); FOREACHC(it, TBotAggroList, otherList._BotAggroList) { CBotAggroEntry* bae = it->second; setAggroMinimumFor(bae->getBot(), bae->finalAggro()*scale, true, bae->getLastHitPlace()); } } void CBotAggroOwner::updateListAndMarkBot(std::vector& botList, float coef) { H_AUTO(AGGRO_ULAMB); std::list botsToRemove; { H_AUTO(AGGRO_ULAMB_buildList); FOREACH(it, TBotAggroList, _BotAggroList) { if (!isAggroValid(it->first)) { botsToRemove.push_back(it->first); continue; } CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(it->second->getBot()); nlassert(entity); // if this entry is no more valid, then isAggroValid is bugged if (entity->_ChooseLastTime!=CTimeInterface::gameCycle()) { entity->_ChooseLastTime = CTimeInterface::gameCycle(); entity->_AggroScore = it->second->finalAggro()*coef; botList.push_back(entity); } else { entity->_AggroScore += it->second->finalAggro()*coef; } } } { H_AUTO(AGGRO_ULAMB_removeInvalids); FOREACH(it, std::list, botsToRemove) { _BotAggroList.erase(*it); } } } void CBotAggroOwner::blockAggro(sint32 blockTime) { if (!_AggroBlocked || _AggroBlockTimer.timeRemaining() botsToRemove; FOREACH(it, TBotAggroList, _BotAggroList) { // :TODO: Check aggro validity (target position), but it's too expensive I think if (it->second->updateTime(ticks)) { botsToRemove.push_back(it->first); } else { CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(it->second->getBot()); if (!entity) botsToRemove.push_back(it->first); } } FOREACH(it, std::deque, botsToRemove) { _BotAggroList.erase(*it); } if (_BotAggroList.empty() && _FirstHitPlace) { if (_ReturnPos.toAIVector().quickDistTo(getAggroPos().toAIVector()) < getReturnDistCheck()) _FirstHitPlace = NULL; } } // :NOTE: If you modify this method modify isNewAggroValid accordingly. bool CBotAggroOwner::isAggroValid(TDataSetRow const& bot) { H_AUTO(AGGRO_isAggroValid); TBotAggroList::iterator it = _BotAggroList.find(bot); // Bot has no aggro if (it==_BotAggroList.end()) return false; // Other bot don't exist CAIEntityPhysical const* entity = CAIS::instance().getEntityPhysical(bot); if (!entity) return false; // Bot fled too far from last time he hit if (!it->second->atPlace(entity)) return false; // He's outside of first hit place if (!this->atPlace(entity)) return false; // He seems OK, let's aggro him return true; } // :NOTE: If you modify this method modify isAggroValid accordingly. bool CBotAggroOwner::isNewAggroValid(TDataSetRow const& bot) { H_AUTO(AGGRO_isNewAggroValid); // Other bot don't exist CAIEntityPhysical const* entity = CAIS::instance().getEntityPhysical(bot); if (!entity) return false; // He's outside of first hit place if (!this->atPlace(entity)) return false; // He seems OK, let's aggro him return true; } bool CBotAggroOwner::haveAggroWithEntity(const TDataSetRow& rowId) const { H_AUTO(AGGRO_haveAggroWithEntity); return _BotAggroList.find(rowId) != _BotAggroList.end(); } void CBotAggroOwner::addAggroFor(TDataSetRow const& bot, float aggro, bool forceReturnAggro, NLMISC::CSmartPtr place, bool transferAggro) { H_AUTO(AGGRO_AA_Total); if (!isAggroable(bot) || (_ReturnAggroIgnored && !forceReturnAggro)) return; if (_DontAggro) return; // If group aggro build a bot list who aggro (self included), and return if (transferAggro && getPrimaryGroupAggroCoef()>0.f) { std::set primaryGroup, secondaryGroup; { H_AUTO(AGGRO_AA_BuildAggroTransferList); primaryGroup = getAggroGroup(true); primaryGroup.erase(this); FOREACH(it, std::set, primaryGroup) { if (*it) { std::set aggroGroup = (*it)->getAggroGroup(false); std::set_difference(aggroGroup.begin(), aggroGroup.end(), primaryGroup.begin(), primaryGroup.end(), std::inserter(secondaryGroup, secondaryGroup.end())); } } secondaryGroup.erase(this); } this->addAggroFor(bot, aggro, forceReturnAggro, place, false); FOREACH(it, std::set, primaryGroup) (*it)->addAggroFor(bot, aggro*getPrimaryGroupAggroCoef(), forceReturnAggro, place, false); FOREACH(it, std::set, secondaryGroup) (*it)->addAggroFor(bot, aggro*getSecondaryGroupAggroCoef(), forceReturnAggro, place, false); return; } #ifdef NL_DEBUG nlassert(aggro<=0); #endif // If we have no firstHitPlace start fight if (!_FirstHitPlace) { H_AUTO(AGGRO_AA_BuildFirstHitPlace); _ReturnPos = getAggroPos(); _FirstHitPlace = buildFirstHitPlace(bot); } // Else verify this bot is still in range else { H_AUTO(AGGRO_AA_CheckFirstHitPlace); // If aggro is not valid for that bot (he's outside of range) if (!isNewAggroValid(bot)) { // Forget him forgetAggroFor(bot, true); // And ignore him return; } } if (!place) { H_AUTO(AGGRO_AA_BuildLastHitPlace); CAIEntityPhysical const* entity = NULL; H_TIME(AGGRO_AA_BLHP_GetEntity, entity = CAIS::instance().getEntityPhysical(bot);); if (entity) { NLMISC::CSmartPtr newPlace(NULL); H_TIME(AGGRO_AA_BLHP_AllocPlace, newPlace = NLMISC::CSmartPtr(new CAIPlaceFastXYR(NULL));); H_TIME(AGGRO_AA_BLHP_ParamPlace, newPlace->setPosAndRadius(AITYPES::vp_auto, entity->pos(), (uint32)(getD2Radius()*1000.f));); place = newPlace; } } _LastHitTime = CTimeInterface::gameCycle(); TBotAggroList::iterator it = _BotAggroList.find(bot); if (it!=_BotAggroList.end()) { H_AUTO(AGGRO_AA_AddAggro); it->second->addAggro(-aggro, place); } else { H_AUTO(AGGRO_AA_CreateAggro); // not found, so add it. // as its the first time, majorate aggro (square its effect).. float firstAggro = 1+aggro; firstAggro = 1-firstAggro*firstAggro; _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, firstAggro, *this, place)))); } } void CBotAggroOwner::setAggroMinimumFor(TDataSetRow const& bot, float aggro, bool forceReturnAggro, NLMISC::CSmartPtr place, bool transferAggro) { H_AUTO(AGGRO_SAM_Total); if (!isAggroable(bot) || (_ReturnAggroIgnored && !forceReturnAggro)) return; if (_DontAggro) return; // If group aggro build a bot list who aggro (self included), and return if (transferAggro && getPrimaryGroupAggroCoef()>0.f) { std::set primaryGroup, secondaryGroup; { H_AUTO(AGGRO_SAM_BuildAggroTransferList); primaryGroup = getAggroGroup(true); primaryGroup.erase(this); FOREACH(it, std::set, primaryGroup) { if (*it) { std::set aggroGroup = (*it)->getAggroGroup(false); std::set_difference(aggroGroup.begin(), aggroGroup.end(), primaryGroup.begin(), primaryGroup.end(), std::inserter(secondaryGroup, secondaryGroup.end())); } } secondaryGroup.erase(this); } this->setAggroMinimumFor(bot, aggro, forceReturnAggro, place, false); FOREACH(it, std::set, primaryGroup) (*it)->setAggroMinimumFor(bot, aggro*getPrimaryGroupAggroCoef(), forceReturnAggro, place, false); FOREACH(it, std::set, secondaryGroup) (*it)->setAggroMinimumFor(bot, aggro*getSecondaryGroupAggroCoef(), forceReturnAggro, place, false); return; } #ifdef NL_DEBUG nlassert(aggro>=0); #endif if (!_FirstHitPlace) { H_AUTO(AGGRO_SAM_BuildFirstHitPlace); _ReturnPos = getAggroPos(); _FirstHitPlace = buildFirstHitPlace(bot); } // Else verify this bot is still in range else { H_AUTO(AGGRO_SAM_CheckFirstHitPlace); // If aggro is not valid for that bot (he's outside of range) if (!isNewAggroValid(bot)) { // Forget him forgetAggroFor(bot, true); // And ignore him return; } } if (!place) { H_AUTO(AGGRO_SAM_BuildLastHitPlace); CAIEntityPhysical const* entity = NULL; H_TIME(AGGRO_SAM_BLHP_GetEntity, entity = CAIS::instance().getEntityPhysical(bot);); if (entity) { NLMISC::CSmartPtr newPlace(NULL); H_TIME(AGGRO_SAM_BLHP_AllocPlace, newPlace = NLMISC::CSmartPtr(new CAIPlaceFastXYR(NULL));); H_TIME(AGGRO_SAM_BLHP_ParamPlace, newPlace->setPosAndRadius(AITYPES::vp_auto, entity->pos(), (uint32)(getD2Radius()*1000.f));); place = newPlace; } } TBotAggroList::iterator it = _BotAggroList.find(bot); if (it!=_BotAggroList.end()) { H_AUTO(AGGRO_SAM_SetMinimum); it->second->setMinimum(aggro, place); } else { H_AUTO(AGGRO_SAM_CreateAggro); // not found, so add it. _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, aggro, *this, place)))); } } void CBotAggroOwner::maximizeAggroFor(TDataSetRow const& bot) { H_AUTO(AGGRO_maximizeAggroFor); if (!isAggroable(bot)) return; if (_DontAggro) return; TBotAggroList::iterator it = _BotAggroList.find(bot); if (it!=_BotAggroList.end()) { it->second->setMinimum(0.999f); FOREACH(it2, TBotAggroList, _BotAggroList) { if (it2!=it) it2->second->scaleBy(0.15f); } } else { _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, 0.999f, *this)))); } } void CBotAggroOwner::minimizeAggroFor(TDataSetRow const& bot) { H_AUTO(AGGRO_minimizeAggroFor); if (!isAggroable(bot)) return; if (_DontAggro) return; TBotAggroList::iterator it = _BotAggroList.find(bot); if (it!=_BotAggroList.end()) { it->second->setMaximum(0.1f); FOREACH(it2, TBotAggroList, _BotAggroList) { if (it2!=it) it2->second->setMinimum(0.15f); } } else { _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, 0.1f, *this)))); } } void CBotAggroOwner::forgetAggroFor(TDataSetRow const& bot, bool forgetDamages) { H_AUTO(AGGRO_forgetAggroFor); _BotAggroList.erase(bot); if (forgetDamages && getAggroOwnerEid()!=NLMISC::CEntityId::Unknown) { CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(bot); if (entity) { // Tell EGS that player is a cheater if (entity->getRyzomType()==RYZOMID::player) { NLMISC::CEntityId targetId = entity->getEntityId(); NLMISC::CEntityId botId = getAggroOwnerEid(); NLNET::CMessage msgout("PLAYER_UNREACHABLE"); msgout.serial(botId); msgout.serial(targetId); sendMessageViaMirror("EGS", msgout); } } } } bool CBotAggroOwner::hasBeenHit(uint32 ticks) const { return (CTimeInterface::gameCycle()-_LastHitTime)<=ticks; } bool CBotAggroOwner::isAggroable(TDataSetRow const& dataSetRow) { H_AUTO(AGGRO_isAggroable); if (CMirrors::getEntityId(dataSetRow).getType()!=RYZOMID::player) return true; CAIEntityPhysical* ep = CAIS::instance().getEntityPhysical(dataSetRow); if (!ep) return false; CBotPlayer const* const player = NLMISC::safe_cast(ep); if (!player) return true; return player->isAggroable(); } RYAI_MAP_CRUNCH::CWorldPosition CBotAggroOwner::getAggroPos() const { return RYAI_MAP_CRUNCH::CWorldPosition(); } NLMISC::CEntityId CBotAggroOwner::getAggroOwnerEid() const { return NLMISC::CEntityId::Unknown; } NLMISC::CSmartPtr CBotAggroOwner::buildFirstHitPlace(TDataSetRow const& aggroBot) const { return NLMISC::CSmartPtr(NULL); } void CBotAggroOwner::clearAggroList(bool sendMessageToEGS) { _SendAggroLostToEGS = sendMessageToEGS; _BotAggroList.clear(); _SendAggroLostToEGS = true; } float CBotAggroOwner::getAggroFor(TDataSetRow const& bot) { TBotAggroList::iterator it = _BotAggroList.find(bot); if (it!=_BotAggroList.end()) return it->second->finalAggro(); else return 0.f; }