1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
20 #include "ai_entity_physical.h"
21 #include "ai_entity_physical_inline.h"
22 #include "ai_instance.h"
23 #include "ai_player.h"
25 //////////////////////////////////////////////////////////////////////////////
27 //////////////////////////////////////////////////////////////////////////////
29 CBotAggroEntry::CBotAggroEntry(TDataSetRow
const& bot
, float aggro
, CBotAggroOwner
const& owner
, NLMISC::CSmartPtr
<CAIPlace
const> place
)
32 , _LastHitPlace(place
)
38 _Owner
.aggroGain(_Bot
);
41 CBotAggroEntry::~CBotAggroEntry()
43 if(_Owner
.getSendAggroLostToEGS())
44 _Owner
.aggroLost(_Bot
);
47 void CBotAggroEntry::operator =(CBotAggroEntry
const& other
)
50 nlassert(&_Owner
==&other
._Owner
); // same owner ?
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
);
61 _LastHitPlace
= place
;
64 bool CBotAggroEntry::updateTime(uint32
const& ticks
) const
66 const_cast<CBotAggroEntry
*>(this)->decrementAggros(ticks
);
70 void CBotAggroEntry::setMinimum(float aggro
, NLMISC::CSmartPtr
<CAIPlace
const> place
)
72 if (aggro
>=0.f
&& aggro
<=1.f
&& _Aggro
<aggro
)
76 _LastHitPlace
= place
;
80 void CBotAggroEntry::setMaximum(float aggro
)
82 if (aggro
>=0.f
&& aggro
<=1.f
&& _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
);
104 //////////////////////////////////////////////////////////////////////////////
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)
161 , _SendAggroLostToEGS(true)
163 , _FirstHitPlace(NULL
)
168 CBotAggroOwner::~CBotAggroOwner()
171 nlassert(_BotAggroList
.size()==0); // have to clear this vector before call destructor (coz of call back).
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
)
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
);
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
);
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
);
246 if (!_AggroBlockTimer
.test())
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
);
260 CAIEntityPhysical
* const entity
= CAIS::instance().getEntityPhysical(it
->second
->getBot());
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
);
282 if (it
==_BotAggroList
.end())
284 // Other bot don't exist
285 CAIEntityPhysical
const* entity
= CAIS::instance().getEntityPhysical(bot
);
288 // Bot fled too far from last time he hit
289 if (!it
->second
->atPlace(entity
))
291 // He's outside of first hit place
292 if (!this->atPlace(entity
))
294 // He seems OK, let's aggro him
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
);
306 // He's outside of first hit place
307 if (!this->atPlace(entity
))
309 // He seems OK, let's aggro him
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
))
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
)
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);
358 // If we have no firstHitPlace start fight
361 H_AUTO(AGGRO_AA_BuildFirstHitPlace
);
362 _ReturnPos
= getAggroPos();
363 _FirstHitPlace
= buildFirstHitPlace(bot
);
365 // Else verify this bot is still in range
368 H_AUTO(AGGRO_AA_CheckFirstHitPlace
);
369 // If aggro is not valid for that bot (he's outside of range)
370 if (!isNewAggroValid(bot
))
373 forgetAggroFor(bot
, true);
380 H_AUTO(AGGRO_AA_BuildLastHitPlace
);
381 CAIEntityPhysical
const* entity
= NULL
;
382 H_TIME(AGGRO_AA_BLHP_GetEntity
, entity
= CAIS::instance().getEntityPhysical(bot
););
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
)););
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
);
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
))
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
)
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);
452 H_AUTO(AGGRO_SAM_BuildFirstHitPlace
);
453 _ReturnPos
= getAggroPos();
454 _FirstHitPlace
= buildFirstHitPlace(bot
);
456 // Else verify this bot is still in range
459 H_AUTO(AGGRO_SAM_CheckFirstHitPlace
);
460 // If aggro is not valid for that bot (he's outside of range)
461 if (!isNewAggroValid(bot
))
464 forgetAggroFor(bot
, true);
471 H_AUTO(AGGRO_SAM_BuildLastHitPlace
);
472 CAIEntityPhysical
const* entity
= NULL
;
473 H_TIME(AGGRO_SAM_BLHP_GetEntity
, entity
= CAIS::instance().getEntityPhysical(bot
););
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
)););
483 TBotAggroList::iterator it
= _BotAggroList
.find(bot
);
484 if (it
!=_BotAggroList
.end())
486 H_AUTO(AGGRO_SAM_SetMinimum
);
487 it
->second
->setMinimum(aggro
, place
);
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
))
506 TBotAggroList::iterator it
= _BotAggroList
.find(bot
);
507 if (it
!=_BotAggroList
.end())
509 it
->second
->setMinimum(0.999f
);
510 FOREACH(it2
, TBotAggroList
, _BotAggroList
)
513 it2
->second
->scaleBy(0.15f
);
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
))
531 TBotAggroList::iterator it
= _BotAggroList
.find(bot
);
532 if (it
!=_BotAggroList
.end())
534 it
->second
->setMaximum(0.1f
);
535 FOREACH(it2
, TBotAggroList
, _BotAggroList
)
538 it2
->second
->setMinimum(0.15f
);
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
);
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
)
582 CAIEntityPhysical
* ep
= CAIS::instance().getEntityPhysical(dataSetRow
);
586 CBotPlayer
const* const player
= NLMISC::safe_cast
<CBotPlayer
const*>(ep
);
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();