Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / ai_service / ai_aggro.cpp
blob5d771f569fa054129b36e8075806d8185678e3b4
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "stdpch.h"
19 #include "ai_aggro.h"
20 #include "ai_entity_physical.h"
21 #include "ai_entity_physical_inline.h"
22 #include "ai_instance.h"
23 #include "ai_player.h"
25 //////////////////////////////////////////////////////////////////////////////
26 // CBotAggroEntry //
27 //////////////////////////////////////////////////////////////////////////////
29 CBotAggroEntry::CBotAggroEntry(TDataSetRow const& bot, float aggro, CBotAggroOwner const& owner, NLMISC::CSmartPtr<CAIPlace const> place)
30 : _Bot(bot)
31 , _Owner(owner)
32 , _LastHitPlace(place)
34 #ifdef NL_DEBUG
35 nlassert(aggro>=0.f);
36 #endif
37 _Aggro = aggro;
38 _Owner.aggroGain(_Bot);
41 CBotAggroEntry::~CBotAggroEntry()
43 if(_Owner.getSendAggroLostToEGS())
44 _Owner.aggroLost(_Bot);
47 void CBotAggroEntry::operator =(CBotAggroEntry const& other)
49 #ifdef NL_DEBUG
50 nlassert(&_Owner==&other._Owner); // same owner ?
51 #endif
52 _Bot = other._Bot;
53 _Aggro = other._Aggro;
56 void CBotAggroEntry::addAggro(float aggro, NLMISC::CSmartPtr<CAIPlace const> place)
58 // prevent to add aggro on a bot more than one time per tick.
59 _Aggro += (1.f-_Aggro)*(aggro);
60 if (place!=NULL)
61 _LastHitPlace = place;
64 bool CBotAggroEntry::updateTime(uint32 const& ticks) const
66 const_cast<CBotAggroEntry*>(this)->decrementAggros(ticks);
67 return _Aggro==0.f;
70 void CBotAggroEntry::setMinimum(float aggro, NLMISC::CSmartPtr<CAIPlace const> place)
72 if (aggro>=0.f && aggro<=1.f && _Aggro<aggro)
74 _Aggro = aggro;
75 if (place)
76 _LastHitPlace = place;
80 void CBotAggroEntry::setMaximum(float aggro)
82 if (aggro>=0.f && aggro<=1.f && _Aggro>aggro)
84 _Aggro = aggro;
88 void CBotAggroEntry::decrementAggros(uint32 ticks)
90 static float const TOTAL_DECAY_TIME_SEC = 120.f; // temporary.
91 static float const AGGRO_DEC = (1.f/(TOTAL_DECAY_TIME_SEC*10.f));
92 float const decay = (float)(ticks*AGGRO_DEC);
94 if (_Aggro>decay)
96 _Aggro -= decay;
98 else
100 _Aggro = 0.f;
104 //////////////////////////////////////////////////////////////////////////////
105 // CBotAggroOwner //
106 //////////////////////////////////////////////////////////////////////////////
108 double CBotAggroOwner::defaultReturnDistCheck = 1.5;
109 uint32 CBotAggroOwner::defaultD1Radius = 100 * 1000; // en mm
110 uint32 CBotAggroOwner::defaultD2Radius = 50 * 1000; // en mm
111 float CBotAggroOwner::defaultPrimaryGroupAggroCoef = 0.f; // %age, entre 0 et 1
112 float CBotAggroOwner::defaultSecondaryGroupAggroCoef = 0.f; // %age, entre 0 et 1
113 uint32 CBotAggroOwner::defaultAggroPropagationRadius = 60; // en m
115 float CBotAggroOwner::getReturnDistCheck() const
117 return AggroReturnDistCheck;
120 float CBotAggroOwner::getD1Radius() const
122 return AggroD1Radius;
125 float CBotAggroOwner::getD2Radius() const
127 return AggroD2Radius;
130 float CBotAggroOwner::getPrimaryGroupAggroDist() const
132 return AggroPrimaryGroupDist;
135 float CBotAggroOwner::getPrimaryGroupAggroCoef() const
137 return AggroPrimaryGroupCoef;
140 float CBotAggroOwner::getSecondaryGroupAggroDist() const
142 return AggroSecondaryGroupDist;
145 float CBotAggroOwner::getSecondaryGroupAggroCoef() const
147 return AggroSecondaryGroupCoef;
150 float CBotAggroOwner::getAggroPropagationRadius() const
152 return AggroPropagationRadius;
156 CBotAggroOwner::CBotAggroOwner()
157 : _LastHitTime(CTimeInterface::gameCycle())
158 , _AggroBlocked(false)
159 , _ReturnAggroIgnored(false)
160 , _DontAggro(false)
161 , _SendAggroLostToEGS(true)
162 , _ReturnPos()
163 , _FirstHitPlace(NULL)
164 //, _State(NoAggro)
168 CBotAggroOwner::~CBotAggroOwner()
170 #ifdef NL_DEBUG
171 nlassert(_BotAggroList.size()==0); // have to clear this vector before call destructor (coz of call back).
172 #endif
175 void CBotAggroOwner::mergeAggroList(CBotAggroOwner const& otherList, float scale)
177 H_AUTO(AGGRO_mergeAggroList);
178 NLMISC::clamp(scale, 0.0f, 1.0f);
180 FOREACHC(it, TBotAggroList, otherList._BotAggroList)
182 CBotAggroEntry* bae = it->second;
183 setAggroMinimumFor(bae->getBot(), bae->finalAggro()*scale, true, bae->getLastHitPlace());
188 void CBotAggroOwner::updateListAndMarkBot(std::vector<CAIEntityPhysical*>& botList, float coef)
190 H_AUTO(AGGRO_ULAMB);
191 std::list<TDataSetRow> botsToRemove;
193 H_AUTO(AGGRO_ULAMB_buildList);
194 FOREACH(it, TBotAggroList, _BotAggroList)
196 if (!isAggroValid(it->first))
198 botsToRemove.push_back(it->first);
199 continue;
201 CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(it->second->getBot());
202 nlassert(entity); // if this entry is no more valid, then isAggroValid is bugged
203 if (entity->_ChooseLastTime!=CTimeInterface::gameCycle())
205 entity->_ChooseLastTime = CTimeInterface::gameCycle();
206 entity->_AggroScore = it->second->finalAggro()*coef;
207 botList.push_back(entity);
209 else
211 entity->_AggroScore += it->second->finalAggro()*coef;
216 H_AUTO(AGGRO_ULAMB_removeInvalids);
217 FOREACH(it, std::list<TDataSetRow>, botsToRemove)
219 _BotAggroList.erase(*it);
224 void CBotAggroOwner::blockAggro(sint32 blockTime)
226 if (!_AggroBlocked || _AggroBlockTimer.timeRemaining()<blockTime)
227 _AggroBlockTimer.set(blockTime);
228 _AggroBlocked = true;
231 void CBotAggroOwner::ignoreReturnAggro(bool ignored)
233 _ReturnAggroIgnored = ignored;
236 void CBotAggroOwner::setCanAggro(bool canAggro)
238 _DontAggro = !canAggro;
241 void CBotAggroOwner::update(uint32 ticks)
243 H_AUTO(AGGRO_update);
244 if (_AggroBlocked)
246 if (!_AggroBlockTimer.test())
247 return;
248 _AggroBlocked = false;
250 std::deque<TDataSetRow> botsToRemove;
251 FOREACH(it, TBotAggroList, _BotAggroList)
253 // :TODO: Check aggro validity (target position), but it's too expensive I think
254 if (it->second->updateTime(ticks))
256 botsToRemove.push_back(it->first);
258 else
260 CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(it->second->getBot());
261 if (!entity)
262 botsToRemove.push_back(it->first);
265 FOREACH(it, std::deque<TDataSetRow>, botsToRemove)
267 _BotAggroList.erase(*it);
269 if (_BotAggroList.empty() && _FirstHitPlace)
271 if (_ReturnPos.toAIVector().quickDistTo(getAggroPos().toAIVector()) < getReturnDistCheck())
272 _FirstHitPlace = NULL;
276 // :NOTE: If you modify this method modify isNewAggroValid accordingly.
277 bool CBotAggroOwner::isAggroValid(TDataSetRow const& bot)
279 H_AUTO(AGGRO_isAggroValid);
280 TBotAggroList::iterator it = _BotAggroList.find(bot);
281 // Bot has no aggro
282 if (it==_BotAggroList.end())
283 return false;
284 // Other bot don't exist
285 CAIEntityPhysical const* entity = CAIS::instance().getEntityPhysical(bot);
286 if (!entity)
287 return false;
288 // Bot fled too far from last time he hit
289 if (!it->second->atPlace(entity))
290 return false;
291 // He's outside of first hit place
292 if (!this->atPlace(entity))
293 return false;
294 // He seems OK, let's aggro him
295 return true;
298 // :NOTE: If you modify this method modify isAggroValid accordingly.
299 bool CBotAggroOwner::isNewAggroValid(TDataSetRow const& bot)
301 H_AUTO(AGGRO_isNewAggroValid);
302 // Other bot don't exist
303 CAIEntityPhysical const* entity = CAIS::instance().getEntityPhysical(bot);
304 if (!entity)
305 return false;
306 // He's outside of first hit place
307 if (!this->atPlace(entity))
308 return false;
309 // He seems OK, let's aggro him
310 return true;
313 bool CBotAggroOwner::haveAggroWithEntity(const TDataSetRow& rowId) const
315 H_AUTO(AGGRO_haveAggroWithEntity);
316 return _BotAggroList.find(rowId) != _BotAggroList.end();
319 void CBotAggroOwner::addAggroFor(TDataSetRow const& bot, float aggro, bool forceReturnAggro, NLMISC::CSmartPtr<CAIPlace const> place, bool transferAggro)
321 H_AUTO(AGGRO_AA_Total);
322 if (!isAggroable(bot) || (_ReturnAggroIgnored && !forceReturnAggro))
323 return;
325 if (_DontAggro)
326 return;
328 // If group aggro build a bot list who aggro (self included), and return
329 if (transferAggro && getPrimaryGroupAggroCoef()>0.f)
331 std::set<CBotAggroOwner*> primaryGroup, secondaryGroup;
333 H_AUTO(AGGRO_AA_BuildAggroTransferList);
334 primaryGroup = getAggroGroup(true);
335 primaryGroup.erase(this);
336 FOREACH(it, std::set<CBotAggroOwner*>, primaryGroup)
338 if (*it)
340 std::set<CBotAggroOwner*> aggroGroup = (*it)->getAggroGroup(false);
341 std::set_difference(aggroGroup.begin(), aggroGroup.end(), primaryGroup.begin(), primaryGroup.end(), std::inserter(secondaryGroup, secondaryGroup.end()));
344 secondaryGroup.erase(this);
346 this->addAggroFor(bot, aggro, forceReturnAggro, place, false);
347 FOREACH(it, std::set<CBotAggroOwner*>, primaryGroup)
348 (*it)->addAggroFor(bot, aggro*getPrimaryGroupAggroCoef(), forceReturnAggro, place, false);
349 FOREACH(it, std::set<CBotAggroOwner*>, secondaryGroup)
350 (*it)->addAggroFor(bot, aggro*getSecondaryGroupAggroCoef(), forceReturnAggro, place, false);
351 return;
354 #ifdef NL_DEBUG
355 nlassert(aggro<=0);
356 #endif
358 // If we have no firstHitPlace start fight
359 if (!_FirstHitPlace)
361 H_AUTO(AGGRO_AA_BuildFirstHitPlace);
362 _ReturnPos = getAggroPos();
363 _FirstHitPlace = buildFirstHitPlace(bot);
365 // Else verify this bot is still in range
366 else
368 H_AUTO(AGGRO_AA_CheckFirstHitPlace);
369 // If aggro is not valid for that bot (he's outside of range)
370 if (!isNewAggroValid(bot))
372 // Forget him
373 forgetAggroFor(bot, true);
374 // And ignore him
375 return;
378 if (!place)
380 H_AUTO(AGGRO_AA_BuildLastHitPlace);
381 CAIEntityPhysical const* entity = NULL;
382 H_TIME(AGGRO_AA_BLHP_GetEntity, entity = CAIS::instance().getEntityPhysical(bot););
383 if (entity)
385 NLMISC::CSmartPtr<CAIPlaceFastXYR> newPlace(NULL);
386 H_TIME(AGGRO_AA_BLHP_AllocPlace, newPlace = NLMISC::CSmartPtr<CAIPlaceFastXYR>(new CAIPlaceFastXYR(NULL)););
387 H_TIME(AGGRO_AA_BLHP_ParamPlace, newPlace->setPosAndRadius(AITYPES::vp_auto, entity->pos(), (uint32)(getD2Radius()*1000.f)););
388 place = newPlace;
392 _LastHitTime = CTimeInterface::gameCycle();
394 TBotAggroList::iterator it = _BotAggroList.find(bot);
395 if (it!=_BotAggroList.end())
397 H_AUTO(AGGRO_AA_AddAggro);
398 it->second->addAggro(-aggro, place);
400 else
402 H_AUTO(AGGRO_AA_CreateAggro);
403 // not found, so add it.
404 // as its the first time, majorate aggro (square its effect)..
405 float firstAggro = 1+aggro;
406 firstAggro = 1-firstAggro*firstAggro;
408 _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, firstAggro, *this, place))));
412 void CBotAggroOwner::setAggroMinimumFor(TDataSetRow const& bot, float aggro, bool forceReturnAggro, NLMISC::CSmartPtr<CAIPlace const> place, bool transferAggro)
414 H_AUTO(AGGRO_SAM_Total);
415 if (!isAggroable(bot) || (_ReturnAggroIgnored && !forceReturnAggro))
416 return;
418 if (_DontAggro)
419 return;
421 // If group aggro build a bot list who aggro (self included), and return
422 if (transferAggro && getPrimaryGroupAggroCoef()>0.f)
424 std::set<CBotAggroOwner*> primaryGroup, secondaryGroup;
426 H_AUTO(AGGRO_SAM_BuildAggroTransferList);
427 primaryGroup = getAggroGroup(true);
428 primaryGroup.erase(this);
429 FOREACH(it, std::set<CBotAggroOwner*>, primaryGroup)
431 if (*it)
433 std::set<CBotAggroOwner*> aggroGroup = (*it)->getAggroGroup(false);
434 std::set_difference(aggroGroup.begin(), aggroGroup.end(), primaryGroup.begin(), primaryGroup.end(), std::inserter(secondaryGroup, secondaryGroup.end()));
437 secondaryGroup.erase(this);
439 this->setAggroMinimumFor(bot, aggro, forceReturnAggro, place, false);
440 FOREACH(it, std::set<CBotAggroOwner*>, primaryGroup)
441 (*it)->setAggroMinimumFor(bot, aggro*getPrimaryGroupAggroCoef(), forceReturnAggro, place, false);
442 FOREACH(it, std::set<CBotAggroOwner*>, secondaryGroup)
443 (*it)->setAggroMinimumFor(bot, aggro*getSecondaryGroupAggroCoef(), forceReturnAggro, place, false);
444 return;
447 #ifdef NL_DEBUG
448 nlassert(aggro>=0);
449 #endif
450 if (!_FirstHitPlace)
452 H_AUTO(AGGRO_SAM_BuildFirstHitPlace);
453 _ReturnPos = getAggroPos();
454 _FirstHitPlace = buildFirstHitPlace(bot);
456 // Else verify this bot is still in range
457 else
459 H_AUTO(AGGRO_SAM_CheckFirstHitPlace);
460 // If aggro is not valid for that bot (he's outside of range)
461 if (!isNewAggroValid(bot))
463 // Forget him
464 forgetAggroFor(bot, true);
465 // And ignore him
466 return;
469 if (!place)
471 H_AUTO(AGGRO_SAM_BuildLastHitPlace);
472 CAIEntityPhysical const* entity = NULL;
473 H_TIME(AGGRO_SAM_BLHP_GetEntity, entity = CAIS::instance().getEntityPhysical(bot););
474 if (entity)
476 NLMISC::CSmartPtr<CAIPlaceFastXYR> newPlace(NULL);
477 H_TIME(AGGRO_SAM_BLHP_AllocPlace, newPlace = NLMISC::CSmartPtr<CAIPlaceFastXYR>(new CAIPlaceFastXYR(NULL)););
478 H_TIME(AGGRO_SAM_BLHP_ParamPlace, newPlace->setPosAndRadius(AITYPES::vp_auto, entity->pos(), (uint32)(getD2Radius()*1000.f)););
479 place = newPlace;
483 TBotAggroList::iterator it = _BotAggroList.find(bot);
484 if (it!=_BotAggroList.end())
486 H_AUTO(AGGRO_SAM_SetMinimum);
487 it->second->setMinimum(aggro, place);
489 else
491 H_AUTO(AGGRO_SAM_CreateAggro);
492 // not found, so add it.
493 _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, aggro, *this, place))));
497 void CBotAggroOwner::maximizeAggroFor(TDataSetRow const& bot)
499 H_AUTO(AGGRO_maximizeAggroFor);
500 if (!isAggroable(bot))
501 return;
503 if (_DontAggro)
504 return;
506 TBotAggroList::iterator it = _BotAggroList.find(bot);
507 if (it!=_BotAggroList.end())
509 it->second->setMinimum(0.999f);
510 FOREACH(it2, TBotAggroList, _BotAggroList)
512 if (it2!=it)
513 it2->second->scaleBy(0.15f);
516 else
518 _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, 0.999f, *this))));
522 void CBotAggroOwner::minimizeAggroFor(TDataSetRow const& bot)
524 H_AUTO(AGGRO_minimizeAggroFor);
525 if (!isAggroable(bot))
526 return;
528 if (_DontAggro)
529 return;
531 TBotAggroList::iterator it = _BotAggroList.find(bot);
532 if (it!=_BotAggroList.end())
534 it->second->setMaximum(0.1f);
535 FOREACH(it2, TBotAggroList, _BotAggroList)
537 if (it2!=it)
538 it2->second->setMinimum(0.15f);
541 else
543 _BotAggroList.insert(std::make_pair(bot, TAggroEntryPtr(new CBotAggroEntry(bot, 0.1f, *this))));
547 void CBotAggroOwner::forgetAggroFor(TDataSetRow const& bot, bool forgetDamages)
549 H_AUTO(AGGRO_forgetAggroFor);
550 _BotAggroList.erase(bot);
551 if (forgetDamages && getAggroOwnerEid()!=NLMISC::CEntityId::Unknown)
553 CAIEntityPhysical* const entity = CAIS::instance().getEntityPhysical(bot);
554 if (entity)
556 // Tell EGS that player is a cheater
557 if (entity->getRyzomType()==RYZOMID::player)
559 NLMISC::CEntityId targetId = entity->getEntityId();
560 NLMISC::CEntityId botId = getAggroOwnerEid();
562 NLNET::CMessage msgout("PLAYER_UNREACHABLE");
563 msgout.serial(botId);
564 msgout.serial(targetId);
565 sendMessageViaMirror("EGS", msgout);
571 bool CBotAggroOwner::hasBeenHit(uint32 ticks) const
573 return (CTimeInterface::gameCycle()-_LastHitTime)<=ticks;
576 bool CBotAggroOwner::isAggroable(TDataSetRow const& dataSetRow)
578 H_AUTO(AGGRO_isAggroable);
579 if (CMirrors::getEntityId(dataSetRow).getType()!=RYZOMID::player)
580 return true;
582 CAIEntityPhysical* ep = CAIS::instance().getEntityPhysical(dataSetRow);
583 if (!ep)
584 return false;
586 CBotPlayer const* const player = NLMISC::safe_cast<CBotPlayer const*>(ep);
587 if (!player)
588 return true;
589 return player->isAggroable();
592 RYAI_MAP_CRUNCH::CWorldPosition CBotAggroOwner::getAggroPos() const
594 return RYAI_MAP_CRUNCH::CWorldPosition();
597 NLMISC::CEntityId CBotAggroOwner::getAggroOwnerEid() const
599 return NLMISC::CEntityId::Unknown;
602 NLMISC::CSmartPtr<CAIPlace const> CBotAggroOwner::buildFirstHitPlace(TDataSetRow const& aggroBot) const
604 return NLMISC::CSmartPtr<CAIPlace const>(NULL);
607 void CBotAggroOwner::clearAggroList(bool sendMessageToEGS)
609 _SendAggroLostToEGS = sendMessageToEGS;
610 _BotAggroList.clear();
611 _SendAggroLostToEGS = true;
614 float CBotAggroOwner::getAggroFor(TDataSetRow const& bot)
616 TBotAggroList::iterator it = _BotAggroList.find(bot);
617 if (it!=_BotAggroList.end())
618 return it->second->finalAggro();
619 else
620 return 0.f;