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_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
;
32 using namespace RYAI_MAP_CRUNCH
;
33 using namespace AITYPES
;
35 /****************************************************************************/
36 /* File configuration */
37 /****************************************************************************/
39 // ---------------------------------------------------------------------------
41 // ---------------------------------------------------------------------------
42 // COMPACT_POS_WARNINGS compress flooding warnings concerning path problems.
43 // Positions where the problems occures are stored and displayed and cleared
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
58 static bool VerboseLog
= false;
61 static bool VerboseLog
= false;
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","")
74 StrToBool (VerboseLog
, args
[0]);
76 log
.displayNL("verboseLogging is %s",VerboseLog
?"ON":"OFF");
80 //----------------------------------------------------------------------------
81 // Control over verbose nature of logging
82 NLMISC_COMMAND(verboseFaunaBot
,"Turn on or off or check the state of verbose fauna bot","")
88 StrToBool (VerboseLog
, args
[0]);
90 nlinfo("VerboseLogging is %s",VerboseLog
?"ON":"OFF");
94 /****************************************************************************/
95 /* Local classes definition and function declatations */
96 /****************************************************************************/
98 //////////////////////////////////////////////////////////////////////////////
99 // CBotProfileGoAway //
100 //////////////////////////////////////////////////////////////////////////////
102 class CBotProfileGoAway
103 : public CAIBaseProfile
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
; }
128 RYAI_MAP_CRUNCH::CDirection _LastDir
;
129 RYAI_MAP_CRUNCH::CMapPosition _LastStartPos
;
131 CPathPosition _PathPos
;
132 CPathCont _fightGoAwayPathContainer
;
138 NLMISC::CSmartPtr
<CAIFaunaActivityBaseSpawnProfile
> _LastProfile
;
141 //////////////////////////////////////////////////////////////////////////////
142 // CBotProfileGoAway //
143 //////////////////////////////////////////////////////////////////////////////
145 class CBotProfileGoAwayFactory
146 : public IAIProfileFactory
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 //////////////////////////////////////////////////////////////////////////////
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.
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)
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.
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
)
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
)
267 case ACTIVITY_RESTING
:
268 setAIProfile(this,&RestFaunaProfileFactory
, false);
270 case ACTIVITY_GRAZING
:
271 setAIProfile(this,&GrazeFaunaProfileFactory
, false);
273 case ACTIVITY_WANDERING
:
274 setAIProfile(this,&WanderFaunaProfileFactory
, false);
276 case ACTIVITY_PLANTIDLE
:
277 setAIProfile(this,&PlanteIdleFaunaProfileFactory
, false);
280 nlwarning("Unsupported activity for fauna bot");
289 // the behaviour update.
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();
308 return AggroReturnDistCheckFauna
;
311 void CSpawnBotFauna::eventEngaged(TDataSetRow
const& originId
)
315 void CSpawnBotFauna::processEvent(CCombatInterface::CEvent
const& event
)
318 if (event
._targetRow
==event
._originatorRow
)
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 !!");
329 if ((event
._nature
==ACTNATURE::FIGHT
|| event
._nature
==ACTNATURE::OFFENSIVE_MAGIC
) && !getPersistent().ignoreOffensiveActions())
331 float aggro
= event
._weight
;
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
);
343 CAIEntityPhysical
*target
=player
->getVisualTarget();
345 player
->setTarget(target
);
348 addAggroFor(event
._originatorRow
, aggro
, true);
352 void CSpawnBotFauna::getBestTarget()
355 CBotFauna
& thisBotFauna
= getPersistent();
357 CGrpFauna
& grp
= thisBotFauna
.grp();
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
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();
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);
402 setVisualTarget(NULL
);
405 if (!_VisualTargetTimer
.test())
412 // (yet) unknown vars
413 float bestScore
= 0.f
;
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
)
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)
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;
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...
453 // ...but in fact whathever we are
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()
463 && thisBotFauna
.getSheet()->getPropertiesCst(AISHEETS::CSheets::getInstance()->playerGroupIndex()).attack()
465 && rootCell
->getFlag()==0) // not in safe zone.
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
)
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
)
486 IMouvementMagnetOwner
* magnetOwner
= dynamic_cast<IMouvementMagnetOwner
*>(getAIProfile());
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
500 // Check for curiosity
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
)
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
;
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
)
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
)) )
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
;
565 //////////////////////////////////////////////////////////////////////////////
566 // The other is a npc
569 // Depending on what we are
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())) )
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
;
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
)
611 setAggroMinimumFor(entity
->dataSetRow(), 0.8f
, false);
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 //////////////////////////////////////////////////////////////
634 // if creature is fighting
635 if (botCreat
->hasBeenHit(20)) // 20 ticks (2 seconds) persistent test ..
637 // if nearest than assist dist ..
639 // or assist compatibility.
640 if ( dist
<thisBotFauna
.getSheet()->AssistDist()
642 || groupProp
.assist())
645 const CAIEntityPhysical
*const target
=botCreat
->getTarget();
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 //////////////////////////////////////////////////////////////
667 if (groupProp
.attack())
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();
687 alpha
=(float)(1/(1+alpha
*0.1));
688 setAggroMinimumFor(entity
->dataSetRow(), 0.8f
*alpha
*0.5f
, false);
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();
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()));
727 //////////////////////////////////////////////////////////////
729 const AISHEETS::CGroupProperties
&groupProp
=botCreat
->getPersistent().getSheet()->getPropertiesCst(thisFaunaGroupIndex
);
731 // the other creature may attack us .. :O
732 if (groupProp
.attack())
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
;
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
)
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
);
770 profile
->setSpeed(speed
);
777 if ( bestVisualScore
==1.f
778 || dist
>(VISUAL_LOOK_AT_DIST
-1.f
))
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
;
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();
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
);
824 setDefaultComportment();
829 thisProfileType
=getAIProfile()->getAIProfileType();
833 if ( thisProfileType
==ACTIVITY_WANDERING
834 || thisProfileType
==ACTIVITY_GRAZING
835 || thisProfileType
==ACTIVITY_RESTING
)
837 setAIProfile(new CCuriosityFaunaProfile(this,curiosityPlayer
->dataSetRow(), getAStarFlag()));
842 if (_TimeBeforeNextCuriosity
.test())
844 _TimeBeforeNextCuriosity
.set((CAIS::rand32(120)+120)*10); // consider every 2 to 4 minutes;
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();
871 return getPersistent().getSheet()->AggroRadiusHungry();
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())
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
906 nlassert(isDestinationValid ());
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();
927 for (uint32 i
=0;i
<nbBots
;i
++)
929 CBotFauna
*bot
=NLMISC::safe_cast
<CBotFauna
*>(bots
[i
]);
933 CSpawnBotFauna
*faunaBot
=bot
->getSpawn();
935 || faunaBot
==&_BotFauna
936 || faunaBot
->getAIProfileType()!=_BotFauna
.getAIProfileType() )
939 IMouvementMagnetOwner
* magnetOwner
= dynamic_cast<IMouvementMagnetOwner
*>(faunaBot
->getAIProfile());
943 const CMovementMagnet
*const movementMagnet
= magnetOwner
->getMovementMagnet();
945 || !movementMagnet
->isDestinationValid())
948 const CAIVector
&destPos
=movementMagnet
->getDestination ();
949 if ( destPos
==_LastDest
950 || destPos
==_PathCont
.getDestination())
953 if ( !faunaBot
->wpos().isValid()
954 || (faunaBot
->wpos().getFlags()&denyFlag
)!=0)
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
)
962 bestScore
= distToBot
;
968 _LastDest
=_PathCont
.getDestination();
969 _PathCont
.setDestination(vp_auto
, bestDest
);
974 // if failed, then try to take an random destination.
976 // here, we have to find another place to go (!)
978 CWorldPosition newPos
;
981 CSpawnGroupFauna
const& grpFauna
= _BotFauna
.spawnGrp();
982 CAIPlace
const* const place
= grpFauna
.targetPlace();
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 )
997 nlassertex(wRndPos
.isValid(), ("Error: can't find a valid pos in place '%s'", _BotFauna
.spawnGrp().targetPlace()->getAliasFullName().c_str()));
999 if (!wRndPos
.isValid())
1000 nlwarning("Error: can't find a valid pos in place '%s'", _BotFauna
.spawnGrp().targetPlace()->getAliasFullName().c_str());
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
)
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());
1026 _PathCont
.setDestination(vp_auto
, newPos
);
1029 void CMovementMagnet::update(uint32 waitTime
, uint32 ticksSinceLastUpdate
, bool ignoreBadPos
)
1031 H_AUTO(MovementMagnet
);
1036 _State
=Movement_Anim
;
1037 _StateTimer
.set((uint32
)((waitTime
*0.5)+CAIS::rand16(waitTime
)));
1038 // drop through to Wait
1041 // this is a small hack to allow migration code to avoid turning
1044 // this is the basic wait code - it waits!
1045 if (_StateTimer
.test())
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())
1061 _State
=Movement_Move
;
1062 getNewDestination (_BotFauna
.wpos(), _denyFlags
); // drop through to Move
1066 if (!_BotFauna
.canMove())
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(
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.
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()))
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);
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
);
1132 CMovementMagnet::getNewDestination(alternativePos
, denyFlag
);
1135 //////////////////////////////////////////////////////////////////////////////
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
)
1149 _Sheet
= CBotFaunaSheetPtr(new CBotFaunaSheet(NULL
));
1150 _Sheet
->setSheet(CBot::getSheet());
1153 CBotFauna::~CBotFauna()
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)
1175 if (!isSpawned() && sendMessage
)
1177 LOG("Cannot spawn a fauna bot %s", getFullName().c_str());
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());
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());
1204 // :KLUDGE: Last part calls a tricky method also called by sheetChanged
1205 // :TODO: Clean that mess
1206 return finalizeSpawnFauna();
1209 void CBotFauna::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).
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;
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();
1260 FOREACHC(it
, std::vector
<NLMISC::CDbgPtr
<CPersistentOfPhysical
> >, bots
)
1262 CAIEntityPhysical
const* const phys
= (*it
)->getSpawnObj();
1263 if (!phys
|| phys
->getRyzomType()!=RYZOMID::creature
)
1266 CSpawnBotFauna
const* const fauna
= NLMISC::safe_cast
<const CSpawnBotFauna
*>(phys
);
1268 && fauna
->getPersistent().getSheet()->FaunaType()==FaunaTypePlant
1269 && fauna
->pos().quickDistTo(pos
.toAIVector())<2)
1279 nlwarning ("Solo Plant Pos Spawn Not Found at: %s", pos
.toString().c_str());
1285 uint32 tries
= 100; // think we won't be so expensive in the average case.
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!
1296 triedPos
= pos
.toAIVector();
1299 std::string
CBotFauna::getOneLineInfoString() const
1301 return std::string("Fauna bot '") + getName() + "'" + "(AliasName : "+getAliasFullName()+")";
1305 void CBotFauna::sheetChanged()
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
);
1320 CMirrors::removeEntity(getSpawnObj()->getEntityId());
1321 setSpawn(NULL
); // automatic smart pointer deletion
1324 // Finalize spawn object creation
1325 if (!finalizeSpawn(botWPos
, spawnTheta
, botMeterSize
))
1328 // :KLUDGE: Both finalizeSpawn and finalizeSpawnFauna are called,
1329 // sheetChanged has a strange herited meaning and may confuse future
1331 // :TODO: Clean that mess and find a more elegant C++ solution to the
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
1342 if (EGSPD::CPeople::Creature
<=sheet
->Race() && sheet
->Race()<EGSPD::CPeople::EndCreature
)
1343 CBot::triggerSetSheet(sheet
);
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
)
1354 , _Bot(NLMISC::safe_cast
<CSpawnBot
*>(owner
))
1355 , _PathPos(NLMISC::safe_cast
<CSpawnBot
*>(owner
)->theta())
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())
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()*/);
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()
1404 for (uint32 pointIndex
=0;pointIndex
<4;pointIndex
++)
1406 const RYAI_MAP_CRUNCH::CWorldPosition
&wpos
=rootCell
->getWorldPosition(pointIndex
);
1410 CCompatibleResult res
;
1411 areCompatiblesWithoutStartRestriction(_Bot
->wpos(), wpos
, _fightGoAwayPathContainer
.getDenyFlags(), res
);
1414 // nlwarning("Error case avoided. Please report this warning to jvuarand.");
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.
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())
1439 _LastStartPos
=_Bot
->wpos();
1441 _fightGoAwayPathContainer
.setDestination(vp_auto
, BestPos
);
1446 // if we found somewhere to go, then go there ..
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(
1456 _fightGoAwayPathContainer
,
1462 if (status
==CFollowPath::FOLLOW_NO_PATH
)
1465 nlwarning("GoAway followpath problem (!!)");
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
)
1486 //////////////////////////////////////////////////////////////////////////////
1487 // Global functions //
1488 //////////////////////////////////////////////////////////////////////////////
1490 static const char *cyclesStateName(CFaunaActivity::TCycleState 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";
1505 return "UNKNOWN STATE";