New lua versions
[ryzomcore.git] / ryzom / server / src / ai_service / ai_bot_fauna.cpp
blob53afcd30a734f963f5386458567610d15ed27762
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/>.
19 #include "stdpch.h"
20 #include "ai_bot_fauna.h"
21 #include "ai_grp_fauna.h"
22 #include "ai_mgr_fauna.h"
23 #include "ai_player.h"
24 #include "ai_bot_npc.h"
25 #include "ai_grp_npc.h"
26 #include "ai_profile_fauna.h"
28 #include "dyn_grp_inline.h"
30 using namespace NLMISC;
31 using namespace std;
32 using namespace RYAI_MAP_CRUNCH;
33 using namespace AITYPES;
35 /****************************************************************************/
36 /* File configuration */
37 /****************************************************************************/
39 // ---------------------------------------------------------------------------
40 // Debug defines
41 // ---------------------------------------------------------------------------
42 // COMPACT_POS_WARNINGS compress flooding warnings concerning path problems.
43 // Positions where the problems occures are stored and displayed and cleared
44 // every minute.
45 // :TODO: /!\ As it cannot be tested without long-time run with several
46 // players the following define can be commented to restore previous behavior.
47 #define COMPACT_POS_WARNINGS 1
49 // ---------------------------------------------------------------------------
50 // Some combat constants
51 static float const CONSIDER_MIN_DIST = 6.f;
53 // ---------------------------------------------------------------------------
55 // ---------------------------------------------------------------------------
56 // Stuff used for management of log messages
57 #ifdef NL_DEBUG
58 static bool VerboseLog = false;
59 #endif
60 #ifndef NL_DEBUG
61 static bool VerboseLog = false;
62 #endif
64 #define LOG if (!VerboseLog) {} else nlinfo
66 // ---------------------------------------------------------------------------
67 // Control over verbose nature of logging
68 NLMISC_COMMAND(verboseFaunaLog,"Turn on or off or check the state of verbose fauna activity logging","")
70 if(args.size()>1)
71 return false;
73 if(args.size()==1)
74 StrToBool (VerboseLog, args[0]);
76 log.displayNL("verboseLogging is %s",VerboseLog?"ON":"OFF");
77 return true;
80 //----------------------------------------------------------------------------
81 // Control over verbose nature of logging
82 NLMISC_COMMAND(verboseFaunaBot,"Turn on or off or check the state of verbose fauna bot","")
84 if(args.size()>1)
85 return false;
87 if(args.size()==1)
88 StrToBool (VerboseLog, args[0]);
90 nlinfo("VerboseLogging is %s",VerboseLog?"ON":"OFF");
91 return true;
94 /****************************************************************************/
95 /* Local classes definition and function declatations */
96 /****************************************************************************/
98 //////////////////////////////////////////////////////////////////////////////
99 // CBotProfileGoAway //
100 //////////////////////////////////////////////////////////////////////////////
102 class CBotProfileGoAway
103 : public CAIBaseProfile
105 public:
106 CBotProfileGoAway(CProfileOwner* owner, RYAI_MAP_CRUNCH::TAStarFlag denyFlags, float speed = 0.f, CAIFaunaActivityBaseSpawnProfile* lastProfile = NULL);
108 virtual void beginProfile();
110 virtual void endProfile() { }
112 // Speed is in the range [0;1]
113 void setSpeed(float speed) { _Speed = speed; }
115 float speed () { return _Speed; }
117 CAIVector& getDecalageRef() { return _Decalage; }
119 CAIFaunaActivityBaseSpawnProfile* lastProfile() const { return _LastProfile; }
121 virtual void updateProfile(uint ticksSinceLastUpdate);
123 virtual std::string getOneLineInfoString() const { return std::string("go_away profile"); }
125 virtual TProfiles getAIProfileType () const { return BOT_GO_AWAY; }
127 public:
128 RYAI_MAP_CRUNCH::CDirection _LastDir;
129 RYAI_MAP_CRUNCH::CMapPosition _LastStartPos;
131 CPathPosition _PathPos;
132 CPathCont _fightGoAwayPathContainer;
134 protected:
135 CAIVector _Decalage;
136 CSpawnBot* _Bot;
137 float _Speed;
138 NLMISC::CSmartPtr<CAIFaunaActivityBaseSpawnProfile> _LastProfile;
141 //////////////////////////////////////////////////////////////////////////////
142 // CBotProfileGoAway //
143 //////////////////////////////////////////////////////////////////////////////
145 class CBotProfileGoAwayFactory
146 : public IAIProfileFactory
148 public:
149 NLMISC::CSmartPtr<IAIProfile> createAIProfile(CProfileOwner* owner);
152 CBotProfileGoAwayFactory BotProfileGoAway;
154 //////////////////////////////////////////////////////////////////////////////
155 // Global functions //
156 //////////////////////////////////////////////////////////////////////////////
158 static const char *cyclesStateName(CFaunaActivity::TCycleState s);
160 /****************************************************************************/
161 /* Function definitions */
162 /****************************************************************************/
164 //////////////////////////////////////////////////////////////////////////////
165 // CSpawnBotFauna //
166 //////////////////////////////////////////////////////////////////////////////
168 CSpawnBotFauna::CSpawnBotFauna(TDataSetRow const& row, CBot& owner, NLMISC::CEntityId const& id, float radius, uint32 level, RYAI_MAP_CRUNCH::TAStarFlag denyFlags)
169 : CSpawnBot(row, owner, id, radius, level, denyFlags)
171 setCycleState(CFaunaActivity::CycleStateUndefined);
173 // we start with a wander activity.
174 setAIProfile(this, &WanderFaunaProfileFactory, false);
176 _NextBestTargetUpdate.set(1); // next.
177 _Hungry = 1.f;
180 CSpawnBotFauna::~CSpawnBotFauna ()
184 CBotFauna& CSpawnBotFauna::getPersistent() const
186 return static_cast<CBotFauna&>(CSpawnBot::getPersistent());
189 bool CSpawnBotFauna::canMove() const
191 return /*!isRooted() && */walkSpeed() != 0 && getPersistent().faunaType()!=AITYPES::FaunaTypePlant;
194 float CSpawnBotFauna::getCollisionDist(float angTo) const
196 // :TODO: Rehabilitate behaviour based on sheet (see this function history in CVS)
197 return radius();
200 void CSpawnBotFauna::sendInfoToEGS() const
202 CFaunaBotDescription &fbd = CAIS::instance().getFaunaDescription();
203 fbd.Bots.push_back(dataSetRow());
204 fbd.GrpAlias.push_back(getPersistent().grp().getAlias());
207 void CSpawnBotFauna::update(TProfiles activity, uint32 ticksSinceLastUpdate)
209 H_AUTO(CSpawnBotFauna_update);
211 nlassert(!getAISpawnProfile().isNull());
212 // this piece of code change the current comportment of the fauna in regards of the group comportment.
214 ++AISStat::BotTotalUpdCtr;
215 ++AISStat::BotFaunaUpdCtr;
217 TProfiles faunaActivity = getAIProfileType();
218 if (faunaActivity==BOT_FLEE && !isFeared() && getUnreachableTarget().isNULL())
220 setDefaultComportment();
221 faunaActivity=getAIProfileType();
225 if ( spawnGrp().mustDespawnBots()
226 && ( spawnGrp().despawnImmediately()
227 || ( faunaActivity!=BOT_FIGHT
228 && faunaActivity!=BOT_FLEE
231 ) // if its group is out of time.
233 spawnGrp().addBotToDespawnAndRespawnTime(&getPersistent(), 1, 2); // .. set the bot to be despawn and gives the control to its group.
234 return;
237 if ( faunaActivity!=ACTIVITY_CORPSE
238 && (spawnGrp().getUpdatePriority()<(2<<2)) ) // 2*40 -> 80 meters
240 if ( faunaActivity!=BOT_FIGHT
241 || !NLMISC::safe_cast<CBotProfileFight*>(getAIProfile())->isHitting())
244 if ( _NextBestTargetUpdate.test()
245 || faunaActivity==BOT_GO_AWAY)
247 getBestTarget ();
248 faunaActivity=getAIProfileType(); // activity could have changed.
249 _NextBestTargetUpdate.set(15); // getbesttarget every 1.5 secs ..
254 if ( faunaActivity==ACTIVITY_GRAZING
255 || faunaActivity==ACTIVITY_RESTING
256 || faunaActivity==ACTIVITY_WANDERING )
258 if (faunaActivity!=activity)
260 CAIFaunaActivityBaseSpawnProfile *profile=NLMISC::safe_cast<CAIFaunaActivityBaseSpawnProfile*>(getAIProfile());
261 if (!(profile->getMovementMagnet().isNull()))
263 if (profile->getMovementMagnet()->getMovementType()==CMovementMagnet::Movement_Move)
265 switch(activity)
267 case ACTIVITY_RESTING:
268 setAIProfile(this,&RestFaunaProfileFactory, false);
269 break;
270 case ACTIVITY_GRAZING:
271 setAIProfile(this,&GrazeFaunaProfileFactory, false);
272 break;
273 case ACTIVITY_WANDERING:
274 setAIProfile(this,&WanderFaunaProfileFactory, false);
275 break;
276 case ACTIVITY_PLANTIDLE:
277 setAIProfile(this,&PlanteIdleFaunaProfileFactory, false);
278 break;
279 default:
280 nlwarning("Unsupported activity for fauna bot");
281 break;
289 // the behaviour update.
290 if (!isStuned())
292 updateProfile(ticksSinceLastUpdate);
294 // if normal activity .. update Aggro.
295 if ( faunaActivity!=BOT_FIGHT
296 && faunaActivity!=BOT_FLEE )
298 this->CBotAggroOwner::update(ticksSinceLastUpdate);
303 float CSpawnBotFauna::getReturnDistCheck() const
305 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroReturnDistCheck()>=0.f)
306 return getPersistent().getSheet()->AggroReturnDistCheck();
307 else
308 return AggroReturnDistCheckFauna;
311 void CSpawnBotFauna::eventEngaged(TDataSetRow const& originId)
315 void CSpawnBotFauna::processEvent(CCombatInterface::CEvent const& event)
317 // no self aggro.
318 if (event._targetRow==event._originatorRow)
319 return;
321 // To remove when debug done ..
322 CAIEntityPhysical *ep = CAIS::instance().getEntityPhysical(event._originatorRow);
323 if (ep==NULL || ep->getAIInstance()->getChildIndex()!=getAIInstance()->getChildIndex())
325 nlwarning("AIInstance Problem !!");
326 return;
329 if ((event._nature==ACTNATURE::FIGHT || event._nature==ACTNATURE::OFFENSIVE_MAGIC) && !getPersistent().ignoreOffensiveActions())
331 float aggro = event._weight;
332 if (aggro>-0.15f)
334 aggro = -0.15f;
336 if (event._nature==ACTNATURE::OFFENSIVE_MAGIC)
338 aggro = (float)((1.f+aggro)*0.5f-1.f); // maximize aggro for magic
339 //insure if aggressor is player, player have it's target seted for BOSS assist
340 CBotPlayer *player=dynamic_cast<CBotPlayer*>(ep);
341 if(player)
343 CAIEntityPhysical *target=player->getVisualTarget();
344 if (target)
345 player->setTarget(target);
348 addAggroFor(event._originatorRow, aggro, true);
352 void CSpawnBotFauna::getBestTarget()
354 // Get ourself
355 CBotFauna& thisBotFauna = getPersistent();
356 // Get our group
357 CGrpFauna& grp = thisBotFauna.grp();
358 // Get our type
359 TFaunaType faunaType = grp.getType();
360 // Compute an aggro radius
361 float const AggroRadius = faunaType==FaunaTypeHerbivore?30.f:aggroRadius();
363 // used if the bot is too far from its group center to use its vision. ???
364 CAIVision<CPersistentOfPhysical> vision;
365 CAIVision<CPersistentOfPhysical>::iterator itVision, itVisionEnd;
367 // VISUAL_LOOK_AT_DIST
368 // ASSistDist
370 float const VISUAL_LOOK_AT_DIST = 10.f;
372 // Compute a test radius
373 float testradius = AggroRadius; // is a minimum for other bot consideration.
374 if (testradius<VISUAL_LOOK_AT_DIST)
375 testradius=VISUAL_LOOK_AT_DIST;
376 if (testradius<thisBotFauna.getSheet()->AssistDist())
377 testradius=thisBotFauna.getSheet()->AssistDist();
378 testradius+=radius();
380 // Get the vision
381 vision.updateBotsAndPlayers(getAIInstance(), CAIVector(pos()), (uint32)(testradius+3.f), (uint32)(testradius+3.f));
382 itVision = vision.begin();
383 itVisionEnd = vision.end();
385 // Vars for the most interesting player
386 float bestCuriosityScore = (radius()+1.5f)*2000.f;
387 CAIEntityPhysical* curiosityPlayer = NULL;
389 // Vars for the most appealing target
390 float bestVisualScore = 0.f;
391 CAIEntityPhysical* visualTarget = NULL;
393 if (!getVisualTarget().isNULL())
395 if ( getTarget().isNULL() // check if we are looking to a too far visual target.
396 && ( _VisualTargetTimer.test()
397 || getVisualTarget()->pos().quickDistTo(pos())>VISUAL_LOOK_AT_DIST ) // if the target is too far ..
400 _VisualTargetTimer.set(CAIS::rand32(40)+25);
401 bestVisualScore=1.f;
402 setVisualTarget(NULL);
405 if (!_VisualTargetTimer.test())
407 bestVisualScore=1.f;
412 // (yet) unknown vars
413 float bestScore = 0.f;
414 bool goAway = false;
415 TProfiles thisProfileType = getAIProfile()->getAIProfileType();
416 uint32 const thisFaunaGroupIndex = thisBotFauna.getSheet()->GroupPropertiesIndex();
417 sint32 const thisHeight = wpos().getMetricHeight ();
419 // For each entity in the visual range
420 for ( ; itVision!=itVisionEnd; ++itVision)
422 // The other entity
423 CAIEntityPhysical* const entity = itVision->getSpawnObj();
425 // If the other entity is not real or if it is us, skip it
426 if (entity==NULL || entity==this)
427 continue;
429 // We compute the vector to the other entity
430 CAIVector delta = pos();
431 delta -= entity->pos();
432 // We compute the dist to it
433 double const dist = delta.quickNorm();
435 // Check that the entity is in the test radius
436 if (dist <= (testradius+entity->radius()))
438 // inf 10 m .. this second test was only put here for test .. need more tuning before generalization.
439 bool const tooHigh = abs(entity->wpos().getMetricHeight()-thisHeight)>10000;
440 if (tooHigh)
441 continue;
443 // Depending on the type of the entity
444 switch (entity->getRyzomType())
446 //////////////////////////////////////////////////////////////////////////////
447 // The other is a player
448 case RYZOMID::player:
450 // Depending what we are...
451 switch(faunaType)
453 // ...but in fact whathever we are
454 case FaunaTypePlant:
455 case FaunaTypeHerbivore:
456 case FaunaTypePredator:
458 // Get the root cell of the player (for safe zone stuff)
459 CRootCell const* const rootCell = entity->wpos().getRootCell();
460 // If player is a valid target and if we dont like him
461 if ( entity->isAlive()
462 && dist<AggroRadius
463 && thisBotFauna.getSheet()->getPropertiesCst(AISHEETS::CSheets::getInstance()->playerGroupIndex()).attack()
464 && rootCell
465 && rootCell->getFlag()==0) // not in safe zone.
467 // Set some aggro
468 setAggroMinimumFor(entity->dataSetRow(), 0.8f*0.5f, false);
470 // If we don't have to aggro, and we're not a plant
471 else if (faunaType!=FaunaTypePlant)
473 // Cast to player
474 CBotPlayer* player = NLMISC::type_cast<CBotPlayer*>(entity);
475 // CBotPlayer* player = NLMISC::safe_cast<CBotPlayer*>(entity);
477 // If player is aggressive and close enough
478 if (player->isAggressive() && dist<std::max(CONSIDER_MIN_DIST, thisBotFauna.getSheet()->AssistDist()))
480 // If we are in an interruptible state
481 if ( thisProfileType==ACTIVITY_GRAZING
482 || thisProfileType==ACTIVITY_RESTING
483 || thisProfileType==ACTIVITY_WANDERING)
485 // Get our profile
486 IMouvementMagnetOwner* magnetOwner = dynamic_cast<IMouvementMagnetOwner*>(getAIProfile());
487 if (magnetOwner)
489 // Get our movement magnet
490 CMovementMagnet* movementMagnet = magnetOwner->getMovementMagnet();
491 // If we have one and we are not moving finish it
492 if (movementMagnet!=NULL && movementMagnet->getMovementType()!=CMovementMagnet::Movement_Move)
493 movementMagnet->stateTimer().set(0);
496 // Get out of the switch, next tick we'll aggro him
497 break;
500 // Check for curiosity
501 if ( canMove()
502 && !player->isAggressive()
503 && entity->wpos().isValid()
504 && (entity->wpos().getFlags()&entity->getAStarFlag())==0)
506 // Suppose we can go to him
507 bool canChange = true;
508 // If we are doing something not very interesting
509 if ( thisProfileType==ACTIVITY_GRAZING
510 || thisProfileType==ACTIVITY_RESTING
511 || thisProfileType==ACTIVITY_WANDERING)
513 // We change only...
514 canChange = false;
515 IMouvementMagnetOwner* magnetOwner = dynamic_cast<IMouvementMagnetOwner*>(getAIProfile());
516 if (magnetOwner && !(magnetOwner->getMovementMagnet().isNull()))
518 // ...if we are just moving
519 canChange = magnetOwner->getMovementMagnet()->getMovementType()==CMovementMagnet::Movement_Move;
523 if (canChange)
525 // Compute a curiosity score, taking into account the time and the dist
526 float const targetterNumber = (float)entity->totalTargeterCount();
527 float const targetterScore = 1 + targetterNumber*targetterNumber*targetterNumber;
528 float const time = (float)_TimeBeforeNextCuriosity.timeRemaining();
529 float const curiosityScore = targetterScore * time * (float)dist;
530 // If it's the most interesting player
531 if (curiosityScore<bestCuriosityScore)
533 // Set it as such
534 bestCuriosityScore=curiosityScore;
535 curiosityPlayer=entity;
540 // If we are already looking at something, or the entity is too far
541 if ( (bestVisualScore==1.f)
542 || (dist>=(VISUAL_LOOK_AT_DIST-1.f)) )
543 // Skip it
544 break;
546 // If we have no visual target
547 if ( !((CAIEntityPhysical*)getVisualTarget())
548 && !((CAIEntityPhysical*)getTarget()))
550 float const score = (float)(1.f/(1.f+dist+CAIS::rand32(7)));
551 if (score>bestVisualScore)
553 bestVisualScore=score;
554 visualTarget = entity;
559 break;
560 default:
561 break;
564 break;
565 //////////////////////////////////////////////////////////////////////////////
566 // The other is a npc
567 case RYZOMID::npc:
569 // Depending on what we are
570 switch(faunaType)
572 // If we're a plant
573 case FaunaTypePlant:
574 // Ignore the npc
575 break;
576 // If we're an herbivore
577 case FaunaTypeHerbivore:
579 // If we already have a visual target or the entity is too far
580 if ( bestVisualScore==1.f
581 || dist>(VISUAL_LOOK_AT_DIST-1.f)
582 || ( ((CAIEntityPhysical*)getVisualTarget())
583 || ((CAIEntityPhysical*)getTarget())) )
584 // Skip it
585 break;
587 // If we are doing something not interesting
588 if ( thisProfileType==ACTIVITY_GRAZING
589 || thisProfileType==ACTIVITY_RESTING
590 || thisProfileType==ACTIVITY_WANDERING)
592 // We look at the npc if he is appealing
593 float const score = (float)(1.f/(1.f+dist+CAIS::rand32(7)));
594 if (score>bestVisualScore)
596 bestVisualScore=score;
597 visualTarget=entity;
601 break;
602 // If we are a predator
603 case FaunaTypePredator:
605 // Get the entity as a npc bot
606 CSpawnBotNpc* botNpc = NLMISC::safe_cast<CSpawnBotNpc*>(entity);
607 // If it is an escorted entity
608 if (botNpc->spawnGrp().activityProfile().getAIProfileType() == ACTIVITY_ESCORTED)
610 // Aggro it
611 setAggroMinimumFor(entity->dataSetRow(), 0.8f, false);
614 break;
615 default:
616 break;
619 break;
620 //////////////////////////////////////////////////////////////////////////////
621 // The other is an animal
622 // :TODO: Finish the doc of that method
623 case RYZOMID::creature:
624 case RYZOMID::pack_animal:
626 CSpawnBot *botCreat=NLMISC::safe_cast<CSpawnBot*>(entity);
627 CGroup &creatGrp=botCreat->getPersistent().getGroup();
628 const uint32 otherCreatGrpIndex=botCreat->getPersistent().getSheet()->GroupPropertiesIndex();
629 const AISHEETS::CGroupProperties &groupProp=thisBotFauna.getSheet()->getPropertiesCst(otherCreatGrpIndex);
631 //////////////////////////////////////////////////////////////
632 // Assist it ?
634 // if creature is fighting
635 if (botCreat->hasBeenHit(20)) // 20 ticks (2 seconds) persistent test ..
637 // if nearest than assist dist ..
638 // and same group
639 // or assist compatibility.
640 if ( dist<thisBotFauna.getSheet()->AssistDist()
641 && ( &creatGrp==&grp
642 || groupProp.assist())
645 const CAIEntityPhysical *const target=botCreat->getTarget();
646 if ( target
647 && target->getRyzomType()==RYZOMID::player)
649 setAggroMinimumFor(target->dataSetRow(), 0.2f, false);
652 // check attackers and give minimum aggro.
653 CAIEntityPhysical const* attacker = botCreat->firstTargeter();
654 while (attacker!=NULL)
656 if (attacker->getRyzomType()==RYZOMID::player)
658 setAggroMinimumFor(attacker->dataSetRow(), 0.2f, false);
660 attacker = attacker->nextTargeter();
665 //////////////////////////////////////////////////////////////
666 // Attack it ?
667 if (groupProp.attack())
669 if ( canMove ()
670 && hungry ()>0 )
672 if (entity->isAlive())
674 if (!((CAIEntityPhysical*)getTarget()))
676 if (runSpeed()>entity->runSpeed())
678 // got enought life ? (more than 75%).
679 if ((4*currentHitPoints())>(3*maxHitPoints()))
681 // check if the herbivore is in the current place
682 const CAIPlace *place=spawnGrp().targetPlace();
683 float alpha((float)place->midPos().quickDistTo(CAIVector(entity->wpos())));
684 alpha -= place->getRadius();
685 if (alpha<0)
686 alpha=0;
687 alpha=(float)(1/(1+alpha*0.1));
688 setAggroMinimumFor(entity->dataSetRow(), 0.8f*alpha*0.5f, false);
693 else
695 if (entity->food()>0)
697 if ( ( ( thisProfileType!=BOT_FIGHT
698 && thisProfileType!=BOT_FLEE )
699 || (((CAIEntityPhysical*)getTarget())==NULL || ((CAIEntityPhysical*)getTarget())==entity) )
700 && thisProfileType!=BOT_GO_AWAY
701 && thisProfileType!=ACTIVITY_EAT_CORPSE )
703 CSpawnBot *botCreat=NLMISC::safe_cast<CSpawnBot*>(entity);
704 // if its a corpse, change our behaviour to eat.
705 IAIProfile* profile = botCreat->getAIProfile();
706 if ( profile
707 && profile->getAIProfileType()==ACTIVITY_CORPSE
708 && botCreat->wpos().isValid()
709 && !(botCreat->wpos().getFlags()&botCreat->getAStarFlag())
712 CCorpseFaunaProfile *corpseProfile=NLMISC::safe_cast<CCorpseFaunaProfile*>(profile);
713 if ( !corpseProfile->haveEater()
714 && !corpseProfile->eated() )
715 { // start a eater comportment.
716 corpseProfile->setEater(true);
717 setAIProfile(new CEatCorpseFaunaProfile(this,entity->dataSetRow(), getAStarFlag()));
725 else
727 //////////////////////////////////////////////////////////////
728 // Flee from it ?
729 const AISHEETS::CGroupProperties &groupProp=botCreat->getPersistent().getSheet()->getPropertiesCst(thisFaunaGroupIndex);
731 // the other creature may attack us .. :O
732 if (groupProp.attack())
734 if ( canMove()
735 && entity->isAlive()
736 && runSpeed()<entity->runSpeed())
738 TProfiles creatActivity=getAIProfileType();
739 if (dist<(testradius*0.5f))
741 if ( creatActivity==ACTIVITY_GRAZING
742 || creatActivity==ACTIVITY_RESTING
743 || creatActivity==ACTIVITY_WANDERING )
745 CAIFaunaActivityBaseSpawnProfile *botProfile=NLMISC::safe_cast<CAIFaunaActivityBaseSpawnProfile*>(getAIProfile());
746 setAIProfile(new CBotProfileGoAway(this,getAStarFlag(), 1.f, botProfile));
747 creatActivity=BOT_GO_AWAY;
749 else
751 if (creatActivity==ACTIVITY_CURIOSITY)
753 setAIProfile(new CBotProfileGoAway(this,getAStarFlag(), 1.f, NULL));
754 creatActivity=BOT_GO_AWAY;
760 if (creatActivity==BOT_GO_AWAY)
762 CAIVector dir=delta;
763 const float qNorm=(float)dir.quickNorm();
764 dir.normalize(1000.f/(qNorm+1.f));
765 CBotProfileGoAway *const profile=NLMISC::safe_cast<CBotProfileGoAway*>(getAIProfile());
766 profile->getDecalageRef()+=dir;
767 float speed=100.f/(qNorm*qNorm+1.f);
768 if (speed>1.f)
769 speed=1.f;
770 profile->setSpeed(speed);
771 goAway=true;
777 if ( bestVisualScore==1.f
778 || dist>(VISUAL_LOOK_AT_DIST-1.f))
779 break;
781 // if no visual target.
782 if (!((CAIEntityPhysical*)getVisualTarget()) && !((CAIEntityPhysical*)getTarget()))
784 if ( thisProfileType==ACTIVITY_GRAZING
785 || thisProfileType==ACTIVITY_RESTING
786 || thisProfileType==ACTIVITY_WANDERING )
788 const float score=(float)(1.f/(1.f+dist+CAIS::rand32(7)));
789 if (score>bestVisualScore)
791 bestVisualScore=score;
792 visualTarget=entity;
797 break;
798 default:
799 break;
801 } // if entity is in test radius
802 } // for each entity in visual range
804 TProfiles faunaActivity=getAIProfileType();
805 if (faunaActivity==BOT_GO_AWAY)
807 CBotProfileGoAway *profile=NLMISC::safe_cast<CBotProfileGoAway*>(getAIProfile());
808 if (!goAway) //profile->decalage().isNull())
810 CAIFaunaActivityBaseSpawnProfile* lastProfile = profile->lastProfile();
811 if (canMove() && lastProfile)
813 IMouvementMagnetOwner* magnetOwner = NLMISC::safe_cast<IMouvementMagnetOwner*>(lastProfile);
814 CMovementMagnet* movementMagnet = magnetOwner->getMovementMagnet();
815 if (movementMagnet)
817 movementMagnet->setState(CMovementMagnet::Movement_Wait_Anim);
818 movementMagnet->stateTimer().set(CAIS::rand32(51)+50); // wait between 5 and 10 seconds.
820 setAIProfile(lastProfile, CProfilePtr::START_RESUME);
822 else
824 setDefaultComportment();
829 thisProfileType=getAIProfile()->getAIProfileType();
831 if (curiosityPlayer)
833 if ( thisProfileType==ACTIVITY_WANDERING
834 || thisProfileType==ACTIVITY_GRAZING
835 || thisProfileType==ACTIVITY_RESTING )
837 setAIProfile(new CCuriosityFaunaProfile(this,curiosityPlayer->dataSetRow(), getAStarFlag()));
840 else
842 if (_TimeBeforeNextCuriosity.test())
844 _TimeBeforeNextCuriosity.set((CAIS::rand32(120)+120)*10); // consider every 2 to 4 minutes;
848 if (visualTarget)
850 thisProfileType=getAIProfile()->getAIProfileType();
851 if ( thisProfileType==ACTIVITY_GRAZING
852 || thisProfileType==ACTIVITY_RESTING
853 || thisProfileType==ACTIVITY_WANDERING )
855 setVisualTarget(visualTarget);
856 _VisualTargetTimer.set(CAIS::rand32(60)+10);
862 float CSpawnBotFauna::aggroRadius()
864 switch (cycleState())
866 case CFaunaActivity::CycleStateVeryHungry:
867 case CFaunaActivity::CycleStateStarving:
868 if (spawnGrp().getPersistent().places()[CGrpFauna::EAT_PLACE]->atPlace(CAIVector(pos())))
869 return getPersistent().getSheet()->AggroRadiusHunting();
870 else
871 return getPersistent().getSheet()->AggroRadiusHungry();
872 default:
873 return getPersistent().getSheet()->AggroRadiusNotHungry();
877 //////////////////////////////////////////////////////////////////////////////
878 // CMovementMagnet //
879 //////////////////////////////////////////////////////////////////////////////
881 CMovementMagnet::CMovementMagnet(CSpawnBotFauna& botFauna, RYAI_MAP_CRUNCH::TAStarFlag flag)
882 : _BotFauna(botFauna)
883 , _PathPos(botFauna.theta())
884 , _PathCont(flag)
885 , _denyFlags(flag)
887 // start by moving during a variable time, just to disperse bot in their place.(must be initialised by constructor)
888 _StateTimer.set(0); // do not wait a long time .. :)
889 _State = Movement_Wait_Anim;
890 _PathPos._Angle = _BotFauna.theta();
891 _Speed = _BotFauna.walkSpeed();
894 CMovementMagnet::~CMovementMagnet()
898 void CMovementMagnet::setBotAngle()
900 _PathPos._Angle = _BotFauna.theta();
903 CAIVector const& CMovementMagnet::getDestination() const
905 #ifdef NL_DEBUG
906 nlassert(isDestinationValid ());
907 #endif
908 return _PathCont.getDestination();
911 bool CMovementMagnet::isDestinationValid() const
913 return _PathCont.getDestPos().isValid();
916 void CMovementMagnet::getNewDestination(RYAI_MAP_CRUNCH::CWorldPosition const& alternativePos, RYAI_MAP_CRUNCH::TAStarFlag denyFlag)
918 if (CAIS::frand()>_BotFauna.getPersistent().getSheet()->GroupDispersion()) // to make some variety.
920 // first, try to take the same way as another bot of the group with the same comportment.
921 CCont<CBot > &bots = _BotFauna.spawnGrp().bots();
922 uint32 nbBots=(uint32)bots.size();
924 float bestScore=0.f;
925 CAIVector bestDest;
927 for (uint32 i=0;i<nbBots;i++)
929 CBotFauna *bot=NLMISC::safe_cast<CBotFauna*>(bots[i]);
930 if (!bot)
931 continue;
933 CSpawnBotFauna *faunaBot=bot->getSpawn();
934 if ( !faunaBot
935 || faunaBot==&_BotFauna
936 || faunaBot->getAIProfileType()!=_BotFauna.getAIProfileType() )
937 continue;
939 IMouvementMagnetOwner* magnetOwner = dynamic_cast<IMouvementMagnetOwner*>(faunaBot->getAIProfile());
940 if (!magnetOwner)
941 continue;
943 const CMovementMagnet *const movementMagnet = magnetOwner->getMovementMagnet();
944 if ( !movementMagnet
945 || !movementMagnet->isDestinationValid())
946 continue;
948 const CAIVector &destPos=movementMagnet->getDestination ();
949 if ( destPos==_LastDest
950 || destPos==_PathCont.getDestination())
951 continue;
953 if ( !faunaBot->wpos().isValid()
954 || (faunaBot->wpos().getFlags()&denyFlag)!=0)
955 continue;
957 // can be optimize by in avoid inversion.
958 const float distToBot=(float)(1.f/(faunaBot->pos().quickDistTo(_BotFauna.pos())+1.f));
959 if (distToBot<bestScore)
960 continue;
962 bestScore = distToBot;
963 bestDest = destPos;
966 if (bestScore>0)
968 _LastDest=_PathCont.getDestination();
969 _PathCont.setDestination(vp_auto, bestDest);
970 return;
974 // if failed, then try to take an random destination.
976 // here, we have to find another place to go (!)
977 uint32 nbTries = 64;
978 CWorldPosition newPos;
979 float bestScore = 0;
981 CSpawnGroupFauna const& grpFauna = _BotFauna.spawnGrp();
982 CAIPlace const* const place = grpFauna.targetPlace();
986 --nbTries;
988 CWorldPosition wRndPos;
989 _BotFauna.spawnGrp().targetPlace()->getRandomPos(wRndPos);
991 // check if its a nogo and water proof position.
992 if ( !wRndPos.isValid()
993 || (wRndPos.getTopologyRef().getCstTopologyNode().getFlags()&denyFlag)!=0 )
994 continue;
996 #if !FINAL_VERSION
997 nlassertex(wRndPos.isValid(), ("Error: can't find a valid pos in place '%s'", _BotFauna.spawnGrp().targetPlace()->getAliasFullName().c_str()));
998 #else
999 if (!wRndPos.isValid())
1000 nlwarning("Error: can't find a valid pos in place '%s'", _BotFauna.spawnGrp().targetPlace()->getAliasFullName().c_str());
1001 #endif
1003 CAIVector const newPosVector = CAIVector(wRndPos);
1005 double const distToGrp = newPosVector.quickDistTo(grpFauna.getCenterPos());
1006 double distToBot = newPosVector.quickDistTo(_BotFauna.pos());
1008 // if too near, then make this score not too good, else add some value to minimize the effect of bot dist / group dist.
1009 distToBot += (distToBot<=1.0)?30.0:4.0;
1011 float const score = (float)(distToGrp/distToBot);
1013 if (score<bestScore)
1014 continue;
1016 bestScore = score;
1017 newPos = wRndPos;
1018 } while (nbTries>0);
1020 if (!newPos.isValid())
1022 _PathCont.setDestination(vp_auto, alternativePos); // the alternative pos given by parameters.
1023 nlwarning("Error: can't find a valid pos in place '%s' near %s", _BotFauna.spawnGrp().targetPlace()->getAliasFullName().c_str(), _BotFauna.spawnGrp().targetPlace()->worldValidPos().toString().c_str());
1025 else
1026 _PathCont.setDestination(vp_auto, newPos);
1029 void CMovementMagnet::update(uint32 waitTime, uint32 ticksSinceLastUpdate, bool ignoreBadPos)
1031 H_AUTO(MovementMagnet);
1033 switch (_State)
1035 BeginAnim:
1036 _State=Movement_Anim;
1037 _StateTimer.set((uint32)((waitTime*0.5)+CAIS::rand16(waitTime)));
1038 // drop through to Wait
1040 case Movement_Anim:
1041 // this is a small hack to allow migration code to avoid turning
1042 if (waitTime==0)
1043 goto BeginMove;
1044 // this is the basic wait code - it waits!
1045 if (_StateTimer.test())
1046 goto BeginWaitAnim;
1047 break;
1049 BeginWaitAnim:
1050 // select a random angle in range +/-pi, biased towards 0
1051 // and setup dTheta and dThetaTimer to avoid turning again at start of movement
1052 _StateTimer.set(100); //secs (wait for the anim to stop)
1053 _State=Movement_Wait_Anim;
1055 case Movement_Wait_Anim:
1056 if (_StateTimer.test())
1057 goto BeginMove;
1058 break;
1060 BeginMove:
1061 _State=Movement_Move;
1062 getNewDestination (_BotFauna.wpos(), _denyFlags); // drop through to Move
1064 case Movement_Move:
1066 if (!_BotFauna.canMove())
1067 break;
1069 float distToDest=(float)_PathCont.getDestination().quickDistTo(_BotFauna.pos());
1070 distToDest-=((_BotFauna.getPersistent().getChildIndex()&7)+1.5f);
1072 _Speed=_BotFauna.walkSpeed();
1073 float dist=_Speed*ticksSinceLastUpdate;
1074 CAIVector lastPos=_BotFauna.pos();
1076 CAIVector deviateVector(CAngle(_BotFauna.theta().asRadians()+(Pi*0.5f)*sin(CTimeInterface::gameCycle()*0.03f+_BotFauna.getPersistent().getChildIndex())).asVector2d());
1077 _BotFauna.setMoveDecalage(_BotFauna.moveDecalage()+deviateVector);
1079 CFollowPath::TFollowStatus status = CFollowPath::getInstance()->followPath(
1080 &_BotFauna,
1081 _PathPos,
1082 _PathCont,
1083 dist,
1084 dist*.5f,
1085 .5f);
1087 if (!ignoreBadPos)
1089 if (status==CFollowPath::FOLLOW_NO_PATH) // No Path Found !
1090 getNewDestination (_BotFauna.wpos(), _denyFlags); // drop through to Move
1092 if (distToDest<=0 || lastPos.quickDistTo(_BotFauna.pos())<(dist*0.5f)) // too much people.
1094 goto BeginAnim;
1098 break;
1102 //////////////////////////////////////////////////////////////////////////////
1103 // CReturnMovementMagnet //
1104 //////////////////////////////////////////////////////////////////////////////
1106 CReturnMovementMagnet::CReturnMovementMagnet(RYAI_MAP_CRUNCH::CWorldPosition const& forcedDest, CSpawnBotFauna& botFauna, RYAI_MAP_CRUNCH::TAStarFlag flag)
1107 : CMovementMagnet(botFauna, flag)
1108 , _ForcedDest(forcedDest)
1111 RYAI_MAP_CRUNCH::CWorldPosition wpos;
1112 if (CWorldContainer::getWorldMap().setWorldPosition(forcedDest.h(), wpos, forcedDest.toAIVector()))
1114 setWPos(wpos);
1116 else
1118 nlerror("Impossible to create a valid world pos for return magnet.")
1121 // sint32 z = forcedDest.h();
1122 // CAIVector const vect = forcedDest.toAIVector();
1123 // bool res = CWorldContainer::getWorldMap().setWorldPosition(z, _ForcedDest, vect);
1124 // nlassert(res);
1127 void CReturnMovementMagnet::getNewDestination(RYAI_MAP_CRUNCH::CWorldPosition const& alternativePos, RYAI_MAP_CRUNCH::TAStarFlag denyFlag)
1129 if (_ForcedDest.isValid() && (_ForcedDest.getTopologyRef().getCstTopologyNode().getFlags()&denyFlag)==0)
1130 _PathCont.setDestination(_ForcedDest);
1131 else
1132 CMovementMagnet::getNewDestination(alternativePos, denyFlag);
1135 //////////////////////////////////////////////////////////////////////////////
1136 // CBotFauna //
1137 //////////////////////////////////////////////////////////////////////////////
1139 CAIS::CCounter& CBotFauna::getSpawnCounter()
1141 return CAIS::instance()._FaunaBotCounter;
1144 CBotFauna::CBotFauna(AITYPES::TFaunaType type, CGroup* owner, CAIAliasDescriptionNode* alias)
1145 : CBot(owner, alias)
1146 , _Type(type)
1147 , _Sheet(NULL)
1149 _Sheet = CBotFaunaSheetPtr(new CBotFaunaSheet(NULL));
1150 _Sheet->setSheet(CBot::getSheet());
1153 CBotFauna::~CBotFauna()
1155 if (isSpawned())
1157 despawnBot();
1161 CSpawnBot* CBotFauna::getSpawnBot(TDataSetRow const& row, NLMISC::CEntityId const& id, float radius)
1163 return new CSpawnBotFauna(row, *this, id, radius, getSheet()->Level(), getGroup().getAStarFlag());
1166 bool CBotFauna::reSpawn(bool sendMessage)
1168 // we made some tries because its an random positionned spawn that may failed some times ..
1169 uint32 maxtries = 100;
1170 while (!spawn() && maxtries>0)
1172 --maxtries;
1175 if (!isSpawned() && sendMessage)
1177 LOG("Cannot spawn a fauna bot %s", getFullName().c_str());
1179 return isSpawned();
1182 // :KLUDGE: This methods is a part of the trick for bot respawn
1183 // :TODO: Clean that mess
1184 bool CBotFauna::finalizeSpawnFauna()
1186 getSpawn()->sendInfoToEGS();
1188 // execute birth script :)
1189 AISHEETS::ICreature::TScriptCompList const& scriptList = getSheet()->BirthScriptList();
1190 FOREACHC(it, AISHEETS::ICreature::TScriptCompList, scriptList)
1191 (*it)->update(*getSpawn());
1192 return true;
1195 // nothing special is made, its a simple bot spawn.
1196 bool CBotFauna::spawn()
1198 // initialise the energy value.
1199 initEnergy(NLMISC::safe_cast<CGrpFauna*>(getOwner())->getEnergyCoef());
1201 if (!CBot::spawn())
1202 return false;
1204 // :KLUDGE: Last part calls a tricky method also called by sheetChanged
1205 // :TODO: Clean that mess
1206 return finalizeSpawnFauna();
1209 void CBotFauna::despawnBot()
1211 CBot::despawnBot();
1214 CGrpFauna& CBotFauna::grp() const
1216 return *static_cast<CGrpFauna*>(getOwner());
1219 CMgrFauna& CBotFauna::mgr() const
1221 return *static_cast<CMgrFauna*>(grp().getOwner());
1224 void CBotFauna::getSpawnPos(CAIVector& triedPos, CWorldPosition& pos, CWorldMap const& worldMap, CAngle& spawnTheta)
1226 nlassert(grp().getSpawnObj());
1227 CSpawnGroupFauna* grpFauna = grp().getSpawnObj();
1228 CBotFauna* leader = grpFauna->leader();
1230 spawnTheta = CAngle(CAIS::frand(2*NLMISC::Pi));
1232 // if possible, fauna must spawn near its leader.
1233 if (leader && leader->isSpawned() && getSheet()->FaunaType()!=FaunaTypePlant)
1235 RYAI_MAP_CRUNCH::CWorldPosition leaderPos=leader->getSpawn()->wpos();
1236 // if the leader is not in the current place ..
1237 if (!grpFauna->targetPlace()->atPlace(leaderPos))
1238 { // spawn the fauna near the leader (same place for instance).
1239 pos = leaderPos;
1243 if (!pos.isValid())
1245 // if we try to spawn a plant, try to find a position where no plant already are.
1246 if (getSheet()->FaunaType()==FaunaTypePlant)
1248 bool rejected = true;
1249 uint32 nbTry = 64;
1250 while (nbTry-- > 0 && rejected)
1252 grpFauna->targetPlace()->getRandomPos(pos);
1254 // Check if this place is Valid for us ..
1255 CAIVision<CPersistentOfPhysical> vision;
1256 vision.updateBotsAndPlayers(getAIInstance(), pos,0,2);
1257 std::vector<NLMISC::CDbgPtr<CPersistentOfPhysical> > const& bots = vision.bots();
1259 rejected = false;
1260 FOREACHC(it, std::vector<NLMISC::CDbgPtr<CPersistentOfPhysical> >, bots)
1262 CAIEntityPhysical const* const phys = (*it)->getSpawnObj();
1263 if (!phys || phys->getRyzomType()!=RYZOMID::creature)
1264 continue;
1266 CSpawnBotFauna const* const fauna = NLMISC::safe_cast<const CSpawnBotFauna*>(phys);
1267 if ( fauna!=NULL
1268 && fauna->getPersistent().getSheet()->FaunaType()==FaunaTypePlant
1269 && fauna->pos().quickDistTo(pos.toAIVector())<2)
1271 rejected = true;
1272 break;
1276 #if !FINAL_VERSION
1277 if (rejected)
1279 nlwarning ("Solo Plant Pos Spawn Not Found at: %s", pos.toString().c_str());
1281 #endif
1283 else
1285 uint32 tries = 100; // think we won't be so expensive in the average case.
1286 while (tries-- > 0)
1288 RYAI_MAP_CRUNCH::CCompatibleResult res;
1289 grpFauna->targetPlace()->getRandomPos(pos);
1290 areCompatiblesWithoutStartRestriction(pos, grpFauna->targetPlace()->worldValidPos(), getGroup().getAStarFlag(), res);
1291 if (res.isValid()) // Cool!
1292 break;
1296 triedPos = pos.toAIVector();
1299 std::string CBotFauna::getOneLineInfoString() const
1301 return std::string("Fauna bot '") + getName() + "'" + "(AliasName : "+getAliasFullName()+")";
1305 void CBotFauna::sheetChanged()
1307 if (getSpawnObj())
1309 // Get bot state
1310 RYAI_MAP_CRUNCH::CWorldPosition botWPos = getSpawnObj()->wpos();
1311 CAngle spawnTheta = getSpawnObj()->theta();
1312 float botMeterSize = getSheet()->Scale()*getSheet()->Radius();
1313 // :TODO: Save profile info
1315 // If stuck bot position may be outside collision and must be recomputed
1316 if (isStuck() || IsRingShard)
1317 getSpawnPos(lastTriedPos, botWPos, CWorldContainer::getWorldMap(), spawnTheta);
1319 // Delete old bot
1320 CMirrors::removeEntity(getSpawnObj()->getEntityId());
1321 setSpawn(NULL); // automatic smart pointer deletion
1322 notifyBotDespawn();
1324 // Finalize spawn object creation
1325 if (!finalizeSpawn(botWPos, spawnTheta, botMeterSize))
1326 return;
1328 // :KLUDGE: Both finalizeSpawn and finalizeSpawnFauna are called,
1329 // sheetChanged has a strange herited meaning and may confuse future
1330 // coders
1331 // :TODO: Clean that mess and find a more elegant C++ solution to the
1332 // problem
1333 finalizeSpawnFauna();
1337 void CBotFauna::triggerSetSheet(AISHEETS::ICreatureCPtr const& sheet)
1339 // :KLUDGE: This test is there to mimick the behaviour of the client. This
1340 // is not the better way to do this, but the client is so... well, you see
1341 // what I mean.
1342 if (EGSPD::CPeople::Creature<=sheet->Race() && sheet->Race()<EGSPD::CPeople::EndCreature)
1343 CBot::triggerSetSheet(sheet);
1344 else
1345 nlwarning("Trying to set a NPC sheet to a creature, sheet change canceled.");
1348 //////////////////////////////////////////////////////////////////////////////
1349 // CBotProfileGoAway //
1350 //////////////////////////////////////////////////////////////////////////////
1352 CBotProfileGoAway::CBotProfileGoAway(CProfileOwner* owner, RYAI_MAP_CRUNCH::TAStarFlag denyFlags, float speed, CAIFaunaActivityBaseSpawnProfile* lastProfile)
1353 : CAIBaseProfile()
1354 , _Bot(NLMISC::safe_cast<CSpawnBot*>(owner))
1355 , _PathPos(NLMISC::safe_cast<CSpawnBot*>(owner)->theta())
1356 , _Speed(speed)
1357 , _LastProfile(lastProfile)
1358 , _fightGoAwayPathContainer(denyFlags)
1362 void CBotProfileGoAway::beginProfile()
1364 _LastDir = RYAI_MAP_CRUNCH::CDirection(RYAI_MAP_CRUNCH::CDirection::UNDEFINED);
1365 _fightGoAwayPathContainer.setDestination(vp_auto, CAIVector(_Bot->pos()));
1366 _Bot->setMode(MBEHAV::NORMAL);
1369 void CBotProfileGoAway::updateProfile(uint ticksSinceLastUpdate)
1371 H_AUTO(BotGoAwayProfileUpdate)
1372 CFollowPathContext fpcBotGoAwayProfileUpdate("BotGoAwayProfileUpdate");
1374 if (!_Bot->canMove())
1375 return;
1377 bool calcDone=true;
1379 if (_Decalage.isNull())
1380 _Decalage.setX(1+_Decalage.x()); // hum ..
1381 RYAI_MAP_CRUNCH::CDirection startDir(_Decalage.x(), _Decalage.y(), true);
1383 // if we need to change our destination.
1384 if ( startDir!=_LastDir
1385 || _fightGoAwayPathContainer.getDestPos(/*_Bot->size()*/).hasSameFullCellId(_Bot->wpos()) )
1387 float BestScore=-1.f;
1388 RYAI_MAP_CRUNCH::CWorldPosition BestPos;
1390 const RYAI_MAP_CRUNCH::CWorldMap &worldMap=CWorldContainer::getWorldMap(/*_Bot->size()*/);
1391 calcDone=false;
1393 sint nbStep=0;
1395 while (nbStep<8)
1397 // try to find a direction around startDir.
1398 RYAI_MAP_CRUNCH::CDirection dir(startDir);
1399 dir.addStep((RYAI_MAP_CRUNCH::CDirection::TDeltaDirection) ((nbStep&1)?(nbStep>>1):(-(nbStep>>1))));
1401 const RYAI_MAP_CRUNCH::CRootCell *rootCell=worldMap.getRootCellCst(_Bot->wpos().stepCell(dir.dx(),dir.dy())); // _Bot->wpos()
1402 if (rootCell)
1404 for (uint32 pointIndex=0;pointIndex<4;pointIndex++)
1406 const RYAI_MAP_CRUNCH::CWorldPosition &wpos=rootCell->getWorldPosition(pointIndex);
1408 if (wpos.isValid())
1410 CCompatibleResult res;
1411 areCompatiblesWithoutStartRestriction(_Bot->wpos(), wpos, _fightGoAwayPathContainer.getDenyFlags(), res);
1412 if (!res.isValid())
1414 // nlwarning("Error case avoided. Please report this warning to jvuarand.");
1415 continue;
1418 CAIVector deltaToDest(wpos-_Bot->wpos());
1419 const float score=(float) CAIVector(wpos-_Bot->wpos()).dot(_Decalage); //*deltaToDest.norm();
1420 if ( score>BestScore
1421 && deltaToDest.quickNorm()>2.f) // minimum distance requires.
1423 BestScore=score;
1424 BestPos=wpos;
1429 nbStep++;
1432 RYAI_MAP_CRUNCH::CCompatibleResult res;
1433 if (_Bot->wpos().isValid() && BestPos.isValid())
1435 areCompatiblesWithoutStartRestriction(_Bot->wpos(), BestPos, _fightGoAwayPathContainer.denyFlags(), res, true);
1436 if (BestPos.isValid() && res.isValid())
1438 _LastDir=startDir;
1439 _LastStartPos=_Bot->wpos();
1440 calcDone=true;
1441 _fightGoAwayPathContainer.setDestination(vp_auto, BestPos);
1446 // if we found somewhere to go, then go there ..
1447 if (calcDone)
1449 const float dist= ((1.f-_Speed)*_Bot->walkSpeed()+_Speed*_Bot->runSpeed())*ticksSinceLastUpdate;
1451 _Decalage.normalize(100.f*1000.f);
1452 _Decalage += CAIVector(_Bot->pos());
1453 CFollowPath::TFollowStatus const status = CFollowPath::getInstance()->followPath(
1454 _Bot,
1455 _PathPos,
1456 _fightGoAwayPathContainer,
1457 dist,
1458 dist*.71f,
1459 .5f,
1460 true,
1461 &_Decalage);
1462 if (status==CFollowPath::FOLLOW_NO_PATH)
1464 #if !FINAL_VERSION
1465 nlwarning("GoAway followpath problem (!!)");
1466 #endif
1469 else
1471 _LastDir=RYAI_MAP_CRUNCH::CDirection(RYAI_MAP_CRUNCH::CDirection::UNDEFINED);
1473 _Decalage=CAIVector(0,0);
1476 //////////////////////////////////////////////////////////////////////////////
1477 // CBotProfileGoAway //
1478 //////////////////////////////////////////////////////////////////////////////
1480 NLMISC::CSmartPtr<IAIProfile> CBotProfileGoAwayFactory::createAIProfile(CProfileOwner* owner)
1482 nlassert(false);
1483 return NULL;
1486 //////////////////////////////////////////////////////////////////////////////
1487 // Global functions //
1488 //////////////////////////////////////////////////////////////////////////////
1490 static const char *cyclesStateName(CFaunaActivity::TCycleState s)
1492 switch (s)
1494 case CFaunaActivity::CycleStateHungry: return "HUNGRY";
1495 case CFaunaActivity::CycleStateVeryHungry: return "VERY_HUNGRY";
1496 case CFaunaActivity::CycleStateStarving: return "STARVING";
1497 case CFaunaActivity::CycleStateDigesting: return "DIGESTING";
1498 case CFaunaActivity::CycleStateTired: return "TIRED";
1499 case CFaunaActivity::CycleStateVeryTired: return "VERY_TIRED";
1500 case CFaunaActivity::CycleStateExhausted: return "EXHAUSTED";
1501 case CFaunaActivity::CycleStateShaking: return "SHAKING";
1502 default:
1503 break;
1505 return "UNKNOWN STATE";