2012-05-29 13:31:11 +00:00
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "stdpch.h"
# include "ai_bot_fauna.h"
# include "ai_grp_fauna.h"
# include "ai_mgr_fauna.h"
# include "ai_player.h"
# include "ai_bot_npc.h"
# include "ai_grp_npc.h"
# include "ai_profile_fauna.h"
# include "dyn_grp_inline.h"
using namespace NLMISC ;
using namespace std ;
using namespace RYAI_MAP_CRUNCH ;
using namespace AITYPES ;
/****************************************************************************/
/* File configuration */
/****************************************************************************/
// ---------------------------------------------------------------------------
// Debug defines
// ---------------------------------------------------------------------------
// COMPACT_POS_WARNINGS compress flooding warnings concerning path problems.
// Positions where the problems occures are stored and displayed and cleared
// every minute.
// :TODO: /!\ As it cannot be tested without long-time run with several
// players the following define can be commented to restore previous behavior.
# define COMPACT_POS_WARNINGS 1
// ---------------------------------------------------------------------------
// Some combat constants
static float const CONSIDER_MIN_DIST = 6.f ;
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Stuff used for management of log messages
# ifdef NL_DEBUG
static bool VerboseLog = false ;
# endif
# ifndef NL_DEBUG
static bool VerboseLog = false ;
# endif
# define LOG if (!VerboseLog) {} else nlinfo
// ---------------------------------------------------------------------------
// Control over verbose nature of logging
NLMISC_COMMAND ( verboseFaunaLog , " Turn on or off or check the state of verbose fauna activity logging " , " " )
{
if ( args . size ( ) > 1 )
return false ;
if ( args . size ( ) = = 1 )
StrToBool ( VerboseLog , args [ 0 ] ) ;
log . displayNL ( " verboseLogging is %s " , VerboseLog ? " ON " : " OFF " ) ;
return true ;
}
//----------------------------------------------------------------------------
// Control over verbose nature of logging
NLMISC_COMMAND ( verboseFaunaBot , " Turn on or off or check the state of verbose fauna bot " , " " )
{
if ( args . size ( ) > 1 )
return false ;
if ( args . size ( ) = = 1 )
StrToBool ( VerboseLog , args [ 0 ] ) ;
nlinfo ( " VerboseLogging is %s " , VerboseLog ? " ON " : " OFF " ) ;
return true ;
}
/****************************************************************************/
/* Local classes definition and function declatations */
/****************************************************************************/
//////////////////////////////////////////////////////////////////////////////
// CBotProfileGoAway //
//////////////////////////////////////////////////////////////////////////////
class CBotProfileGoAway
: public CAIBaseProfile
{
public :
CBotProfileGoAway ( CProfileOwner * owner , RYAI_MAP_CRUNCH : : TAStarFlag denyFlags , float speed = 0.f , CAIFaunaActivityBaseSpawnProfile * lastProfile = NULL ) ;
virtual void beginProfile ( ) ;
virtual void endProfile ( ) { }
// Speed is in the range [0;1]
void setSpeed ( float speed ) { _Speed = speed ; }
float speed ( ) { return _Speed ; }
CAIVector & getDecalageRef ( ) { return _Decalage ; }
CAIFaunaActivityBaseSpawnProfile * lastProfile ( ) const { return _LastProfile ; }
virtual void updateProfile ( uint ticksSinceLastUpdate ) ;
virtual std : : string getOneLineInfoString ( ) const { return std : : string ( " go_away profile " ) ; }
virtual TProfiles getAIProfileType ( ) const { return BOT_GO_AWAY ; }
public :
RYAI_MAP_CRUNCH : : CDirection _LastDir ;
RYAI_MAP_CRUNCH : : CMapPosition _LastStartPos ;
CPathPosition _PathPos ;
CPathCont _fightGoAwayPathContainer ;
protected :
CAIVector _Decalage ;
CSpawnBot * _Bot ;
float _Speed ;
NLMISC : : CSmartPtr < CAIFaunaActivityBaseSpawnProfile > _LastProfile ;
} ;
//////////////////////////////////////////////////////////////////////////////
// CBotProfileGoAway //
//////////////////////////////////////////////////////////////////////////////
class CBotProfileGoAwayFactory
: public IAIProfileFactory
{
public :
NLMISC : : CSmartPtr < IAIProfile > createAIProfile ( CProfileOwner * owner ) ;
} ;
CBotProfileGoAwayFactory BotProfileGoAway ;
//////////////////////////////////////////////////////////////////////////////
// Global functions //
//////////////////////////////////////////////////////////////////////////////
static const char * cyclesStateName ( CFaunaActivity : : TCycleState s ) ;
/****************************************************************************/
/* Function definitions */
/****************************************************************************/
//////////////////////////////////////////////////////////////////////////////
// CSpawnBotFauna //
//////////////////////////////////////////////////////////////////////////////
CSpawnBotFauna : : CSpawnBotFauna ( TDataSetRow const & row , CBot & owner , NLMISC : : CEntityId const & id , float radius , uint32 level , RYAI_MAP_CRUNCH : : TAStarFlag denyFlags )
: CSpawnBot ( row , owner , id , radius , level , denyFlags )
{
setCycleState ( CFaunaActivity : : CycleStateUndefined ) ;
// we start with a wander activity.
setAIProfile ( this , & WanderFaunaProfileFactory , false ) ;
_NextBestTargetUpdate . set ( 1 ) ; // next.
_Hungry = 1.f ;
}
CSpawnBotFauna : : ~ CSpawnBotFauna ( )
{
}
CBotFauna & CSpawnBotFauna : : getPersistent ( ) const
{
return static_cast < CBotFauna & > ( CSpawnBot : : getPersistent ( ) ) ;
}
bool CSpawnBotFauna : : canMove ( ) const
{
return /*!isRooted() && */ walkSpeed ( ) ! = 0 & & getPersistent ( ) . faunaType ( ) ! = AITYPES : : FaunaTypePlant ;
}
float CSpawnBotFauna : : getCollisionDist ( float angTo ) const
{
// :TODO: Rehabilitate behaviour based on sheet (see this function history in CVS)
return radius ( ) ;
}
void CSpawnBotFauna : : sendInfoToEGS ( ) const
{
CFaunaBotDescription & fbd = CAIS : : instance ( ) . getFaunaDescription ( ) ;
fbd . Bots . push_back ( dataSetRow ( ) ) ;
fbd . GrpAlias . push_back ( getPersistent ( ) . grp ( ) . getAlias ( ) ) ;
}
void CSpawnBotFauna : : update ( TProfiles activity , uint32 ticksSinceLastUpdate )
{
H_AUTO ( CSpawnBotFauna_update ) ;
nlassert ( ! getAISpawnProfile ( ) . isNull ( ) ) ;
// this piece of code change the current comportment of the fauna in regards of the group comportment.
+ + AISStat : : BotTotalUpdCtr ;
+ + AISStat : : BotFaunaUpdCtr ;
TProfiles faunaActivity = getAIProfileType ( ) ;
if ( faunaActivity = = BOT_FLEE & & ! isFeared ( ) & & getUnreachableTarget ( ) . isNULL ( ) )
{
setDefaultComportment ( ) ;
faunaActivity = getAIProfileType ( ) ;
}
{
if ( spawnGrp ( ) . mustDespawnBots ( )
& & ( spawnGrp ( ) . despawnImmediately ( )
| | ( faunaActivity ! = BOT_FIGHT
& & faunaActivity ! = BOT_FLEE
)
)
) // if its group is out of time.
{
spawnGrp ( ) . addBotToDespawnAndRespawnTime ( & getPersistent ( ) , 1 , 2 ) ; // .. set the bot to be despawn and gives the control to its group.
return ;
}
if ( faunaActivity ! = ACTIVITY_CORPSE
& & ( spawnGrp ( ) . getUpdatePriority ( ) < ( 2 < < 2 ) ) ) // 2*40 -> 80 meters
{
if ( faunaActivity ! = BOT_FIGHT
| | ! NLMISC : : safe_cast < CBotProfileFight * > ( getAIProfile ( ) ) - > isHitting ( ) )
{
if ( _NextBestTargetUpdate . test ( )
| | faunaActivity = = BOT_GO_AWAY )
{
getBestTarget ( ) ;
faunaActivity = getAIProfileType ( ) ; // activity could have changed.
_NextBestTargetUpdate . set ( 15 ) ; // getbesttarget every 1.5 secs ..
}
}
}
if ( faunaActivity = = ACTIVITY_GRAZING
| | faunaActivity = = ACTIVITY_RESTING
| | faunaActivity = = ACTIVITY_WANDERING )
{
if ( faunaActivity ! = activity )
{
CAIFaunaActivityBaseSpawnProfile * profile = NLMISC : : safe_cast < CAIFaunaActivityBaseSpawnProfile * > ( getAIProfile ( ) ) ;
if ( ! ( profile - > getMovementMagnet ( ) . isNull ( ) ) )
{
if ( profile - > getMovementMagnet ( ) - > getMovementType ( ) = = CMovementMagnet : : Movement_Move )
{
switch ( activity )
{
case ACTIVITY_RESTING :
setAIProfile ( this , & RestFaunaProfileFactory , false ) ;
break ;
case ACTIVITY_GRAZING :
setAIProfile ( this , & GrazeFaunaProfileFactory , false ) ;
break ;
case ACTIVITY_WANDERING :
setAIProfile ( this , & WanderFaunaProfileFactory , false ) ;
break ;
case ACTIVITY_PLANTIDLE :
setAIProfile ( this , & PlanteIdleFaunaProfileFactory , false ) ;
break ;
default :
nlwarning ( " Unsupported activity for fauna bot " ) ;
break ;
}
}
}
}
}
}
// the behaviour update.
if ( ! isStuned ( ) )
{
updateProfile ( ticksSinceLastUpdate ) ;
// if normal activity .. update Aggro.
if ( faunaActivity ! = BOT_FIGHT
& & faunaActivity ! = BOT_FLEE )
{
this - > CBotAggroOwner : : update ( ticksSinceLastUpdate ) ;
}
}
}
float CSpawnBotFauna : : getReturnDistCheck ( ) const
{
if ( getPersistent ( ) . isSheetValid ( ) & & getPersistent ( ) . getSheet ( ) - > AggroReturnDistCheck ( ) > = 0.f )
return getPersistent ( ) . getSheet ( ) - > AggroReturnDistCheck ( ) ;
else
return AggroReturnDistCheckFauna ;
}
void CSpawnBotFauna : : eventEngaged ( TDataSetRow const & originId )
{
}
void CSpawnBotFauna : : processEvent ( CCombatInterface : : CEvent const & event )
{
// no self aggro.
if ( event . _targetRow = = event . _originatorRow )
return ;
// To remove when debug done ..
CAIEntityPhysical * ep = CAIS : : instance ( ) . getEntityPhysical ( event . _originatorRow ) ;
if ( ep = = NULL | | ep - > getAIInstance ( ) - > getChildIndex ( ) ! = getAIInstance ( ) - > getChildIndex ( ) )
{
nlwarning ( " AIInstance Problem !! " ) ;
return ;
}
if ( ( event . _nature = = ACTNATURE : : FIGHT | | event . _nature = = ACTNATURE : : OFFENSIVE_MAGIC ) & & ! getPersistent ( ) . ignoreOffensiveActions ( ) )
{
float aggro = event . _weight ;
if ( aggro > - 0.15f )
{
aggro = - 0.15f ;
}
if ( event . _nature = = ACTNATURE : : OFFENSIVE_MAGIC )
{
aggro = ( float ) ( ( 1.f + aggro ) * 0.5f - 1.f ) ; // maximize aggro for magic
//insure if aggressor is player, player have it's target seted for BOSS assist
CBotPlayer * player = dynamic_cast < CBotPlayer * > ( ep ) ;
if ( player )
{
CAIEntityPhysical * target = player - > getVisualTarget ( ) ;
if ( target )
player - > setTarget ( target ) ;
}
}
addAggroFor ( event . _originatorRow , aggro , true ) ;
}
}
void CSpawnBotFauna : : getBestTarget ( )
{
// Get ourself
CBotFauna & thisBotFauna = getPersistent ( ) ;
// Get our group
CGrpFauna & grp = thisBotFauna . grp ( ) ;
// Get our type
TFaunaType faunaType = grp . getType ( ) ;
// Compute an aggro radius
float const AggroRadius = faunaType = = FaunaTypeHerbivore ? 30.f : aggroRadius ( ) ;
// used if the bot is too far from its group center to use its vision. ???
CAIVision < CPersistentOfPhysical > vision ;
CAIVision < CPersistentOfPhysical > : : iterator itVision , itVisionEnd ;
// VISUAL_LOOK_AT_DIST
// ASSistDist
float const VISUAL_LOOK_AT_DIST = 10.f ;
// Compute a test radius
float testradius = AggroRadius ; // is a minimum for other bot consideration.
if ( testradius < VISUAL_LOOK_AT_DIST )
testradius = VISUAL_LOOK_AT_DIST ;
if ( testradius < thisBotFauna . getSheet ( ) - > AssistDist ( ) )
testradius = thisBotFauna . getSheet ( ) - > AssistDist ( ) ;
testradius + = radius ( ) ;
// Get the vision
vision . updateBotsAndPlayers ( getAIInstance ( ) , CAIVector ( pos ( ) ) , ( uint32 ) ( testradius + 3.f ) , ( uint32 ) ( testradius + 3.f ) ) ;
itVision = vision . begin ( ) ;
itVisionEnd = vision . end ( ) ;
// Vars for the most interesting player
float bestCuriosityScore = ( radius ( ) + 1.5f ) * 2000.f ;
CAIEntityPhysical * curiosityPlayer = NULL ;
// Vars for the most appealing target
float bestVisualScore = 0.f ;
CAIEntityPhysical * visualTarget = NULL ;
if ( ! getVisualTarget ( ) . isNULL ( ) )
{
if ( getTarget ( ) . isNULL ( ) // check if we are looking to a too far visual target.
& & ( _VisualTargetTimer . test ( )
| | getVisualTarget ( ) - > pos ( ) . quickDistTo ( pos ( ) ) > VISUAL_LOOK_AT_DIST ) // if the target is too far ..
)
{
_VisualTargetTimer . set ( CAIS : : rand32 ( 40 ) + 25 ) ;
bestVisualScore = 1.f ;
setVisualTarget ( NULL ) ;
}
if ( ! _VisualTargetTimer . test ( ) )
{
bestVisualScore = 1.f ;
}
}
// (yet) unknown vars
float bestScore = 0.f ;
bool goAway = false ;
TProfiles thisProfileType = getAIProfile ( ) - > getAIProfileType ( ) ;
uint32 const thisFaunaGroupIndex = thisBotFauna . getSheet ( ) - > GroupPropertiesIndex ( ) ;
sint32 const thisHeight = wpos ( ) . getMetricHeight ( ) ;
// For each entity in the visual range
for ( ; itVision ! = itVisionEnd ; + + itVision )
{
// The other entity
CAIEntityPhysical * const entity = itVision - > getSpawnObj ( ) ;
// If the other entity is not real or if it is us, skip it
if ( entity = = NULL | | entity = = this )
continue ;
// We compute the vector to the other entity
CAIVector delta = pos ( ) ;
delta - = entity - > pos ( ) ;
// We compute the dist to it
double const dist = delta . quickNorm ( ) ;
// Check that the entity is in the test radius
if ( dist < = ( testradius + entity - > radius ( ) ) )
{
// inf 10 m .. this second test was only put here for test .. need more tuning before generalization.
bool const tooHigh = abs ( entity - > wpos ( ) . getMetricHeight ( ) - thisHeight ) > 10000 ;
if ( tooHigh )
continue ;
// Depending on the type of the entity
switch ( entity - > getRyzomType ( ) )
{
//////////////////////////////////////////////////////////////////////////////
// The other is a player
case RYZOMID : : player :
{
// Depending what we are...
switch ( faunaType )
{
// ...but in fact whathever we are
case FaunaTypePlant :
case FaunaTypeHerbivore :
case FaunaTypePredator :
{
// Get the root cell of the player (for safe zone stuff)
CRootCell const * const rootCell = entity - > wpos ( ) . getRootCell ( ) ;
// If player is a valid target and if we dont like him
if ( entity - > isAlive ( )
& & dist < AggroRadius
& & thisBotFauna . getSheet ( ) - > getPropertiesCst ( AISHEETS : : CSheets : : getInstance ( ) - > playerGroupIndex ( ) ) . attack ( )
& & rootCell
& & rootCell - > getFlag ( ) = = 0 ) // not in safe zone.
{
// Set some aggro
setAggroMinimumFor ( entity - > dataSetRow ( ) , 0.8f * 0.5f , false ) ;
}
// If we don't have to aggro, and we're not a plant
else if ( faunaType ! = FaunaTypePlant )
{
// Cast to player
CBotPlayer * player = NLMISC : : type_cast < CBotPlayer * > ( entity ) ;
// CBotPlayer* player = NLMISC::safe_cast<CBotPlayer*>(entity);
// If player is aggressive and close enough
if ( player - > isAggressive ( ) & & dist < std : : max ( CONSIDER_MIN_DIST , thisBotFauna . getSheet ( ) - > AssistDist ( ) ) )
{
// If we are in an interruptible state
if ( thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING
| | thisProfileType = = ACTIVITY_WANDERING )
{
// Get our profile
IMouvementMagnetOwner * magnetOwner = dynamic_cast < IMouvementMagnetOwner * > ( getAIProfile ( ) ) ;
if ( magnetOwner )
{
// Get our movement magnet
CMovementMagnet * movementMagnet = magnetOwner - > getMovementMagnet ( ) ;
// If we have one and we are not moving finish it
if ( movementMagnet ! = NULL & & movementMagnet - > getMovementType ( ) ! = CMovementMagnet : : Movement_Move )
movementMagnet - > stateTimer ( ) . set ( 0 ) ;
}
}
// Get out of the switch, next tick we'll aggro him
break ;
}
// Check for curiosity
if ( canMove ( )
& & ! player - > isAggressive ( )
& & entity - > wpos ( ) . isValid ( )
& & ( entity - > wpos ( ) . getFlags ( ) & entity - > getAStarFlag ( ) ) = = 0 )
{
// Suppose we can go to him
bool canChange = true ;
// If we are doing something not very interesting
if ( thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING
| | thisProfileType = = ACTIVITY_WANDERING )
{
// We change only...
canChange = false ;
IMouvementMagnetOwner * magnetOwner = dynamic_cast < IMouvementMagnetOwner * > ( getAIProfile ( ) ) ;
if ( magnetOwner & & ! ( magnetOwner - > getMovementMagnet ( ) . isNull ( ) ) )
{
// ...if we are just moving
canChange = magnetOwner - > getMovementMagnet ( ) - > getMovementType ( ) = = CMovementMagnet : : Movement_Move ;
}
}
if ( canChange )
{
// Compute a curiosity score, taking into account the time and the dist
float const targetterNumber = ( float ) entity - > totalTargeterCount ( ) ;
float const targetterScore = 1 + targetterNumber * targetterNumber * targetterNumber ;
float const time = ( float ) _TimeBeforeNextCuriosity . timeRemaining ( ) ;
float const curiosityScore = targetterScore * time * ( float ) dist ;
// If it's the most interesting player
if ( curiosityScore < bestCuriosityScore )
{
// Set it as such
bestCuriosityScore = curiosityScore ;
curiosityPlayer = entity ;
}
}
}
// If we are already looking at something, or the entity is too far
if ( ( bestVisualScore = = 1.f )
| | ( dist > = ( VISUAL_LOOK_AT_DIST - 1.f ) ) )
// Skip it
break ;
// If we have no visual target
if ( ! ( ( CAIEntityPhysical * ) getVisualTarget ( ) )
& & ! ( ( CAIEntityPhysical * ) getTarget ( ) ) )
{
float const score = ( float ) ( 1.f / ( 1.f + dist + CAIS : : rand32 ( 7 ) ) ) ;
if ( score > bestVisualScore )
{
bestVisualScore = score ;
visualTarget = entity ;
}
}
}
}
break ;
default :
break ;
}
}
break ;
//////////////////////////////////////////////////////////////////////////////
// The other is a npc
case RYZOMID : : npc :
{
// Depending on what we are
switch ( faunaType )
{
// If we're a plant
case FaunaTypePlant :
// Ignore the npc
break ;
// If we're an herbivore
case FaunaTypeHerbivore :
{
// If we already have a visual target or the entity is too far
if ( bestVisualScore = = 1.f
| | dist > ( VISUAL_LOOK_AT_DIST - 1.f )
| | ( ( ( CAIEntityPhysical * ) getVisualTarget ( ) )
| | ( ( CAIEntityPhysical * ) getTarget ( ) ) ) )
// Skip it
break ;
// If we are doing something not interesting
if ( thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING
| | thisProfileType = = ACTIVITY_WANDERING )
{
// We look at the npc if he is appealing
float const score = ( float ) ( 1.f / ( 1.f + dist + CAIS : : rand32 ( 7 ) ) ) ;
if ( score > bestVisualScore )
{
bestVisualScore = score ;
visualTarget = entity ;
}
}
}
break ;
// If we are a predator
case FaunaTypePredator :
{
// Get the entity as a npc bot
CSpawnBotNpc * botNpc = NLMISC : : safe_cast < CSpawnBotNpc * > ( entity ) ;
// If it is an escorted entity
if ( botNpc - > spawnGrp ( ) . activityProfile ( ) . getAIProfileType ( ) = = ACTIVITY_ESCORTED )
{
// Aggro it
setAggroMinimumFor ( entity - > dataSetRow ( ) , 0.8f , false ) ;
}
}
break ;
default :
break ;
}
}
break ;
//////////////////////////////////////////////////////////////////////////////
// The other is an animal
// :TODO: Finish the doc of that method
case RYZOMID : : creature :
case RYZOMID : : pack_animal :
{
CSpawnBot * botCreat = NLMISC : : safe_cast < CSpawnBot * > ( entity ) ;
CGroup & creatGrp = botCreat - > getPersistent ( ) . getGroup ( ) ;
const uint32 otherCreatGrpIndex = botCreat - > getPersistent ( ) . getSheet ( ) - > GroupPropertiesIndex ( ) ;
const AISHEETS : : CGroupProperties & groupProp = thisBotFauna . getSheet ( ) - > getPropertiesCst ( otherCreatGrpIndex ) ;
//////////////////////////////////////////////////////////////
// Assist it ?
// if creature is fighting
if ( botCreat - > hasBeenHit ( 20 ) ) // 20 ticks (2 seconds) persistent test ..
{
// if nearest than assist dist ..
// and same group
// or assist compatibility.
if ( dist < thisBotFauna . getSheet ( ) - > AssistDist ( )
& & ( & creatGrp = = & grp
| | groupProp . assist ( ) )
)
{
const CAIEntityPhysical * const target = botCreat - > getTarget ( ) ;
if ( target
& & target - > getRyzomType ( ) = = RYZOMID : : player )
{
setAggroMinimumFor ( target - > dataSetRow ( ) , 0.2f , false ) ;
}
// check attackers and give minimum aggro.
CAIEntityPhysical const * attacker = botCreat - > firstTargeter ( ) ;
while ( attacker ! = NULL )
{
if ( attacker - > getRyzomType ( ) = = RYZOMID : : player )
{
setAggroMinimumFor ( attacker - > dataSetRow ( ) , 0.2f , false ) ;
}
attacker = attacker - > nextTargeter ( ) ;
}
}
}
//////////////////////////////////////////////////////////////
// Attack it ?
if ( groupProp . attack ( ) )
{
if ( canMove ( )
& & hungry ( ) > 0 )
{
if ( entity - > isAlive ( ) )
{
if ( ! ( ( CAIEntityPhysical * ) getTarget ( ) ) )
{
if ( runSpeed ( ) > entity - > runSpeed ( ) )
{
// got enought life ? (more than 75%).
if ( ( 4 * currentHitPoints ( ) ) > ( 3 * maxHitPoints ( ) ) )
{
// check if the herbivore is in the current place
const CAIPlace * place = spawnGrp ( ) . targetPlace ( ) ;
float alpha ( ( float ) place - > midPos ( ) . quickDistTo ( CAIVector ( entity - > wpos ( ) ) ) ) ;
alpha - = place - > getRadius ( ) ;
if ( alpha < 0 )
alpha = 0 ;
alpha = ( float ) ( 1 / ( 1 + alpha * 0.1 ) ) ;
setAggroMinimumFor ( entity - > dataSetRow ( ) , 0.8f * alpha * 0.5f , false ) ;
}
}
}
}
else
{
if ( entity - > food ( ) > 0 )
{
if ( ( ( thisProfileType ! = BOT_FIGHT
& & thisProfileType ! = BOT_FLEE )
| | ( ( ( CAIEntityPhysical * ) getTarget ( ) ) = = NULL | | ( ( CAIEntityPhysical * ) getTarget ( ) ) = = entity ) )
& & thisProfileType ! = BOT_GO_AWAY
& & thisProfileType ! = ACTIVITY_EAT_CORPSE )
{
CSpawnBot * botCreat = NLMISC : : safe_cast < CSpawnBot * > ( entity ) ;
// if its a corpse, change our behaviour to eat.
IAIProfile * profile = botCreat - > getAIProfile ( ) ;
if ( profile
& & profile - > getAIProfileType ( ) = = ACTIVITY_CORPSE
& & botCreat - > wpos ( ) . isValid ( )
& & ! ( botCreat - > wpos ( ) . getFlags ( ) & botCreat - > getAStarFlag ( ) )
)
{
CCorpseFaunaProfile * corpseProfile = NLMISC : : safe_cast < CCorpseFaunaProfile * > ( profile ) ;
if ( ! corpseProfile - > haveEater ( )
& & ! corpseProfile - > eated ( ) )
{ // start a eater comportment.
corpseProfile - > setEater ( true ) ;
setAIProfile ( new CEatCorpseFaunaProfile ( this , entity - > dataSetRow ( ) , getAStarFlag ( ) ) ) ;
}
}
}
}
}
}
}
else
{
//////////////////////////////////////////////////////////////
// Flee from it ?
const AISHEETS : : CGroupProperties & groupProp = botCreat - > getPersistent ( ) . getSheet ( ) - > getPropertiesCst ( thisFaunaGroupIndex ) ;
// the other creature may attack us .. :O
if ( groupProp . attack ( ) )
{
if ( canMove ( )
& & entity - > isAlive ( )
& & runSpeed ( ) < entity - > runSpeed ( ) )
{
TProfiles creatActivity = getAIProfileType ( ) ;
if ( dist < ( testradius * 0.5f ) )
{
if ( creatActivity = = ACTIVITY_GRAZING
| | creatActivity = = ACTIVITY_RESTING
| | creatActivity = = ACTIVITY_WANDERING )
{
CAIFaunaActivityBaseSpawnProfile * botProfile = NLMISC : : safe_cast < CAIFaunaActivityBaseSpawnProfile * > ( getAIProfile ( ) ) ;
setAIProfile ( new CBotProfileGoAway ( this , getAStarFlag ( ) , 1.f , botProfile ) ) ;
creatActivity = BOT_GO_AWAY ;
}
else
{
if ( creatActivity = = ACTIVITY_CURIOSITY )
{
setAIProfile ( new CBotProfileGoAway ( this , getAStarFlag ( ) , 1.f , NULL ) ) ;
creatActivity = BOT_GO_AWAY ;
}
}
}
if ( creatActivity = = BOT_GO_AWAY )
{
CAIVector dir = delta ;
const float qNorm = ( float ) dir . quickNorm ( ) ;
dir . normalize ( 1000.f / ( qNorm + 1.f ) ) ;
CBotProfileGoAway * const profile = NLMISC : : safe_cast < CBotProfileGoAway * > ( getAIProfile ( ) ) ;
profile - > getDecalageRef ( ) + = dir ;
float speed = 100.f / ( qNorm * qNorm + 1.f ) ;
if ( speed > 1.f )
speed = 1.f ;
profile - > setSpeed ( speed ) ;
goAway = true ;
}
}
}
}
if ( bestVisualScore = = 1.f
| | dist > ( VISUAL_LOOK_AT_DIST - 1.f ) )
break ;
// if no visual target.
if ( ! ( ( CAIEntityPhysical * ) getVisualTarget ( ) ) & & ! ( ( CAIEntityPhysical * ) getTarget ( ) ) )
{
if ( thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING
| | thisProfileType = = ACTIVITY_WANDERING )
{
const float score = ( float ) ( 1.f / ( 1.f + dist + CAIS : : rand32 ( 7 ) ) ) ;
if ( score > bestVisualScore )
{
bestVisualScore = score ;
visualTarget = entity ;
}
}
}
}
break ;
default :
break ;
}
} // if entity is in test radius
} // for each entity in visual range
TProfiles faunaActivity = getAIProfileType ( ) ;
if ( faunaActivity = = BOT_GO_AWAY )
{
CBotProfileGoAway * profile = NLMISC : : safe_cast < CBotProfileGoAway * > ( getAIProfile ( ) ) ;
if ( ! goAway ) //profile->decalage().isNull())
{
CAIFaunaActivityBaseSpawnProfile * lastProfile = profile - > lastProfile ( ) ;
if ( canMove ( ) & & lastProfile )
{
IMouvementMagnetOwner * magnetOwner = NLMISC : : safe_cast < IMouvementMagnetOwner * > ( lastProfile ) ;
CMovementMagnet * movementMagnet = magnetOwner - > getMovementMagnet ( ) ;
if ( movementMagnet )
{
movementMagnet - > setState ( CMovementMagnet : : Movement_Wait_Anim ) ;
movementMagnet - > stateTimer ( ) . set ( CAIS : : rand32 ( 51 ) + 50 ) ; // wait between 5 and 10 seconds.
}
setAIProfile ( lastProfile , CProfilePtr : : START_RESUME ) ;
}
else
{
setDefaultComportment ( ) ;
}
}
}
thisProfileType = getAIProfile ( ) - > getAIProfileType ( ) ;
if ( curiosityPlayer )
{
if ( thisProfileType = = ACTIVITY_WANDERING
| | thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING )
{
setAIProfile ( new CCuriosityFaunaProfile ( this , curiosityPlayer - > dataSetRow ( ) , getAStarFlag ( ) ) ) ;
}
}
else
{
if ( _TimeBeforeNextCuriosity . test ( ) )
{
_TimeBeforeNextCuriosity . set ( ( CAIS : : rand32 ( 120 ) + 120 ) * 10 ) ; // consider every 2 to 4 minutes;
}
}
if ( visualTarget )
{
thisProfileType = getAIProfile ( ) - > getAIProfileType ( ) ;
if ( thisProfileType = = ACTIVITY_GRAZING
| | thisProfileType = = ACTIVITY_RESTING
| | thisProfileType = = ACTIVITY_WANDERING )
{
setVisualTarget ( visualTarget ) ;
_VisualTargetTimer . set ( CAIS : : rand32 ( 60 ) + 10 ) ;
}
}
}
float CSpawnBotFauna : : aggroRadius ( )
{
switch ( cycleState ( ) )
{
case CFaunaActivity : : CycleStateVeryHungry :
case CFaunaActivity : : CycleStateStarving :
if ( spawnGrp ( ) . getPersistent ( ) . places ( ) [ CGrpFauna : : EAT_PLACE ] - > atPlace ( CAIVector ( pos ( ) ) ) )
return getPersistent ( ) . getSheet ( ) - > AggroRadiusHunting ( ) ;
else
return getPersistent ( ) . getSheet ( ) - > AggroRadiusHungry ( ) ;
default :
return getPersistent ( ) . getSheet ( ) - > AggroRadiusNotHungry ( ) ;
}
}
//////////////////////////////////////////////////////////////////////////////
// CMovementMagnet //
//////////////////////////////////////////////////////////////////////////////
CMovementMagnet : : CMovementMagnet ( CSpawnBotFauna & botFauna , RYAI_MAP_CRUNCH : : TAStarFlag flag )
: _BotFauna ( botFauna )
, _PathPos ( botFauna . theta ( ) )
, _PathCont ( flag )
, _denyFlags ( flag )
{
// start by moving during a variable time, just to disperse bot in their place.(must be initialised by constructor)
_StateTimer . set ( 0 ) ; // do not wait a long time .. :)
_State = Movement_Wait_Anim ;
_PathPos . _Angle = _BotFauna . theta ( ) ;
_Speed = _BotFauna . walkSpeed ( ) ;
}
CMovementMagnet : : ~ CMovementMagnet ( )
{
}
void CMovementMagnet : : setBotAngle ( )
{
_PathPos . _Angle = _BotFauna . theta ( ) ;
}
CAIVector const & CMovementMagnet : : getDestination ( ) const
{
# ifdef NL_DEBUG
nlassert ( isDestinationValid ( ) ) ;
# endif
return _PathCont . getDestination ( ) ;
}
bool CMovementMagnet : : isDestinationValid ( ) const
{
return _PathCont . getDestPos ( ) . isValid ( ) ;
}
void CMovementMagnet : : getNewDestination ( RYAI_MAP_CRUNCH : : CWorldPosition const & alternativePos , RYAI_MAP_CRUNCH : : TAStarFlag denyFlag )
{
if ( CAIS : : frand ( ) > _BotFauna . getPersistent ( ) . getSheet ( ) - > GroupDispersion ( ) ) // to make some variety.
{
// first, try to take the same way as another bot of the group with the same comportment.
CCont < CBot > & bots = _BotFauna . spawnGrp ( ) . bots ( ) ;
uint32 nbBots = ( uint32 ) bots . size ( ) ;
float bestScore = 0.f ;
CAIVector bestDest ;
for ( uint32 i = 0 ; i < nbBots ; i + + )
{
CBotFauna * bot = NLMISC : : safe_cast < CBotFauna * > ( bots [ i ] ) ;
if ( ! bot )
continue ;
CSpawnBotFauna * faunaBot = bot - > getSpawn ( ) ;
if ( ! faunaBot
| | faunaBot = = & _BotFauna
| | faunaBot - > getAIProfileType ( ) ! = _BotFauna . getAIProfileType ( ) )
continue ;
IMouvementMagnetOwner * magnetOwner = dynamic_cast < IMouvementMagnetOwner * > ( faunaBot - > getAIProfile ( ) ) ;
if ( ! magnetOwner )
continue ;
const CMovementMagnet * const movementMagnet = magnetOwner - > getMovementMagnet ( ) ;
if ( ! movementMagnet
| | ! movementMagnet - > isDestinationValid ( ) )
continue ;
const CAIVector & destPos = movementMagnet - > getDestination ( ) ;
if ( destPos = = _LastDest
| | destPos = = _PathCont . getDestination ( ) )
continue ;
if ( ! faunaBot - > wpos ( ) . isValid ( )
| | ( faunaBot - > wpos ( ) . getFlags ( ) & denyFlag ) ! = 0 )
continue ;
// can be optimize by in avoid inversion.
const float distToBot = ( float ) ( 1.f / ( faunaBot - > pos ( ) . quickDistTo ( _BotFauna . pos ( ) ) + 1.f ) ) ;
if ( distToBot < bestScore )
continue ;
bestScore = distToBot ;
bestDest = destPos ;
}
if ( bestScore > 0 )
{
_LastDest = _PathCont . getDestination ( ) ;
_PathCont . setDestination ( vp_auto , bestDest ) ;
return ;
}
}
// if failed, then try to take an random destination.
// here, we have to find another place to go (!)
uint32 nbTries = 64 ;
CWorldPosition newPos ;
float bestScore = 0 ;
CSpawnGroupFauna const & grpFauna = _BotFauna . spawnGrp ( ) ;
CAIPlace const * const place = grpFauna . targetPlace ( ) ;
do
{
- - nbTries ;
CWorldPosition wRndPos ;
_BotFauna . spawnGrp ( ) . targetPlace ( ) - > getRandomPos ( wRndPos ) ;
// check if its a nogo and water proof position.
if ( ! wRndPos . isValid ( )
| | ( wRndPos . getTopologyRef ( ) . getCstTopologyNode ( ) . getFlags ( ) & denyFlag ) ! = 0 )
continue ;
# if !FINAL_VERSION
nlassertex ( wRndPos . isValid ( ) , ( " Error: can't find a valid pos in place '%s' " , _BotFauna . spawnGrp ( ) . targetPlace ( ) - > getAliasFullName ( ) . c_str ( ) ) ) ;
# else
if ( ! wRndPos . isValid ( ) )
nlwarning ( " Error: can't find a valid pos in place '%s' " , _BotFauna . spawnGrp ( ) . targetPlace ( ) - > getAliasFullName ( ) . c_str ( ) ) ;
# endif
CAIVector const newPosVector = CAIVector ( wRndPos ) ;
double const distToGrp = newPosVector . quickDistTo ( grpFauna . getCenterPos ( ) ) ;
double distToBot = newPosVector . quickDistTo ( _BotFauna . pos ( ) ) ;
// if too near, then make this score not too good, else add some value to minimize the effect of bot dist / group dist.
distToBot + = ( distToBot < = 1.0 ) ? 30.0 : 4.0 ;
float const score = ( float ) ( distToGrp / distToBot ) ;
if ( score < bestScore )
continue ;
bestScore = score ;
newPos = wRndPos ;
} while ( nbTries > 0 ) ;
if ( ! newPos . isValid ( ) )
{
_PathCont . setDestination ( vp_auto , alternativePos ) ; // the alternative pos given by parameters.
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 ( ) ) ;
}
else
_PathCont . setDestination ( vp_auto , newPos ) ;
}
void CMovementMagnet : : update ( uint32 waitTime , uint32 ticksSinceLastUpdate , bool ignoreBadPos )
{
H_AUTO ( MovementMagnet ) ;
switch ( _State )
{
BeginAnim :
_State = Movement_Anim ;
_StateTimer . set ( ( uint32 ) ( ( waitTime * 0.5 ) + CAIS : : rand16 ( waitTime ) ) ) ;
// drop through to Wait
case Movement_Anim :
// this is a small hack to allow migration code to avoid turning
if ( waitTime = = 0 )
goto BeginMove ;
// this is the basic wait code - it waits!
if ( _StateTimer . test ( ) )
goto BeginWaitAnim ;
break ;
BeginWaitAnim :
// select a random angle in range +/-pi, biased towards 0
// and setup dTheta and dThetaTimer to avoid turning again at start of movement
_StateTimer . set ( 100 ) ; //secs (wait for the anim to stop)
_State = Movement_Wait_Anim ;
case Movement_Wait_Anim :
if ( _StateTimer . test ( ) )
goto BeginMove ;
break ;
BeginMove :
_State = Movement_Move ;
getNewDestination ( _BotFauna . wpos ( ) , _denyFlags ) ; // drop through to Move
_Speed = _BotFauna . walkSpeed ( ) ;
case Movement_Move :
{
if ( ! _BotFauna . canMove ( ) )
break ;
float distToDest = ( float ) _PathCont . getDestination ( ) . quickDistTo ( _BotFauna . pos ( ) ) ;
distToDest - = ( ( _BotFauna . getPersistent ( ) . getChildIndex ( ) & 7 ) + 1.5f ) ;
float dist = _Speed * ticksSinceLastUpdate ;
CAIVector lastPos = _BotFauna . pos ( ) ;
{
CAIVector deviateVector ( CAngle ( _BotFauna . theta ( ) . asRadians ( ) + ( Pi * 0.5f ) * sin ( CTimeInterface : : gameCycle ( ) * 0.03f + _BotFauna . getPersistent ( ) . getChildIndex ( ) ) ) . asVector2d ( ) ) ;
_BotFauna . setMoveDecalage ( _BotFauna . moveDecalage ( ) + deviateVector ) ;
}
CFollowPath : : TFollowStatus status = CFollowPath : : getInstance ( ) - > followPath (
& _BotFauna ,
_PathPos ,
_PathCont ,
dist ,
dist * .5f ,
.5f ) ;
if ( ! ignoreBadPos )
{
if ( status = = CFollowPath : : FOLLOW_NO_PATH ) // No Path Found !
getNewDestination ( _BotFauna . wpos ( ) , _denyFlags ) ; // drop through to Move
if ( distToDest < = 0 | | lastPos . quickDistTo ( _BotFauna . pos ( ) ) < ( dist * 0.5f ) ) // too much people.
{
goto BeginAnim ;
}
}
}
break ;
}
}
//////////////////////////////////////////////////////////////////////////////
// CReturnMovementMagnet //
//////////////////////////////////////////////////////////////////////////////
CReturnMovementMagnet : : CReturnMovementMagnet ( RYAI_MAP_CRUNCH : : CWorldPosition const & forcedDest , CSpawnBotFauna & botFauna , RYAI_MAP_CRUNCH : : TAStarFlag flag )
: CMovementMagnet ( botFauna , flag )
, _ForcedDest ( forcedDest )
{
/*
RYAI_MAP_CRUNCH : : CWorldPosition wpos ;
if ( CWorldContainer : : getWorldMap ( ) . setWorldPosition ( forcedDest . h ( ) , wpos , forcedDest . toAIVector ( ) ) )
{
setWPos ( wpos ) ;
}
else
{
nlerror ( " Impossible to create a valid world pos for return magnet. " )
}
*/
// sint32 z = forcedDest.h();
// CAIVector const vect = forcedDest.toAIVector();
// bool res = CWorldContainer::getWorldMap().setWorldPosition(z, _ForcedDest, vect);
// nlassert(res);
}
void CReturnMovementMagnet : : getNewDestination ( RYAI_MAP_CRUNCH : : CWorldPosition const & alternativePos , RYAI_MAP_CRUNCH : : TAStarFlag denyFlag )
{
if ( _ForcedDest . isValid ( ) & & ( _ForcedDest . getTopologyRef ( ) . getCstTopologyNode ( ) . getFlags ( ) & denyFlag ) = = 0 )
_PathCont . setDestination ( _ForcedDest ) ;
else
CMovementMagnet : : getNewDestination ( alternativePos , denyFlag ) ;
}
//////////////////////////////////////////////////////////////////////////////
// CBotFauna //
//////////////////////////////////////////////////////////////////////////////
CAIS : : CCounter & CBotFauna : : getSpawnCounter ( )
{
return CAIS : : instance ( ) . _FaunaBotCounter ;
}
CBotFauna : : CBotFauna ( AITYPES : : TFaunaType type , CGroup * owner , CAIAliasDescriptionNode * alias )
: CBot ( owner , alias )
, _Type ( type )
, _Sheet ( NULL )
{
_Sheet = CBotFaunaSheetPtr ( new CBotFaunaSheet ( NULL ) ) ;
_Sheet - > setSheet ( CBot : : getSheet ( ) ) ;
}
CBotFauna : : ~ CBotFauna ( )
{
if ( isSpawned ( ) )
{
despawnBot ( ) ;
}
}
CSpawnBot * CBotFauna : : getSpawnBot ( TDataSetRow const & row , NLMISC : : CEntityId const & id , float radius )
{
return new CSpawnBotFauna ( row , * this , id , radius , getSheet ( ) - > Level ( ) , getGroup ( ) . getAStarFlag ( ) ) ;
}
bool CBotFauna : : reSpawn ( bool sendMessage )
{
// we made some tries because its an random positionned spawn that may failed some times ..
uint32 maxtries = 100 ;
while ( ! spawn ( ) & & maxtries > 0 )
{
- - maxtries ;
}
if ( ! isSpawned ( ) & & sendMessage )
{
LOG ( " Cannot spawn a fauna bot %s " , getFullName ( ) . c_str ( ) ) ;
}
return isSpawned ( ) ;
}
// :KLUDGE: This methods is a part of the trick for bot respawn
// :TODO: Clean that mess
bool CBotFauna : : finalizeSpawnFauna ( )
{
getSpawn ( ) - > sendInfoToEGS ( ) ;
// execute birth script :)
AISHEETS : : ICreature : : TScriptCompList const & scriptList = getSheet ( ) - > BirthScriptList ( ) ;
FOREACHC ( it , AISHEETS : : ICreature : : TScriptCompList , scriptList )
( * it ) - > update ( * getSpawn ( ) ) ;
return true ;
}
// nothing special is made, its a simple bot spawn.
bool CBotFauna : : spawn ( )
{
// initialise the energy value.
initEnergy ( NLMISC : : safe_cast < CGrpFauna * > ( getOwner ( ) ) - > getEnergyCoef ( ) ) ;
if ( ! CBot : : spawn ( ) )
return false ;
// :KLUDGE: Last part calls a tricky method also called by sheetChanged
// :TODO: Clean that mess
return finalizeSpawnFauna ( ) ;
}
void CBotFauna : : despawnBot ( )
{
CBot : : despawnBot ( ) ;
}
CGrpFauna & CBotFauna : : grp ( ) const
{
return * static_cast < CGrpFauna * > ( getOwner ( ) ) ;
}
CMgrFauna & CBotFauna : : mgr ( ) const
{
return * static_cast < CMgrFauna * > ( grp ( ) . getOwner ( ) ) ;
}
void CBotFauna : : getSpawnPos ( CAIVector & triedPos , CWorldPosition & pos , CWorldMap const & worldMap , CAngle & spawnTheta )
{
nlassert ( grp ( ) . getSpawnObj ( ) ) ;
CSpawnGroupFauna * grpFauna = grp ( ) . getSpawnObj ( ) ;
CBotFauna * leader = grpFauna - > leader ( ) ;
spawnTheta = CAngle ( CAIS : : frand ( 2 * NLMISC : : Pi ) ) ;
// if possible, fauna must spawn near its leader.
if ( leader & & leader - > isSpawned ( ) & & getSheet ( ) - > FaunaType ( ) ! = FaunaTypePlant )
{
RYAI_MAP_CRUNCH : : CWorldPosition leaderPos = leader - > getSpawn ( ) - > wpos ( ) ;
// if the leader is not in the current place ..
if ( ! grpFauna - > targetPlace ( ) - > atPlace ( leaderPos ) )
{ // spawn the fauna near the leader (same place for instance).
pos = leaderPos ;
}
}
if ( ! pos . isValid ( ) )
{
// if we try to spawn a plant, try to find a position where no plant already are.
if ( getSheet ( ) - > FaunaType ( ) = = FaunaTypePlant )
{
bool rejected = true ;
uint32 nbTry = 64 ;
while ( nbTry - - > 0 & & rejected )
{
grpFauna - > targetPlace ( ) - > getRandomPos ( pos ) ;
// Check if this place is Valid for us ..
CAIVision < CPersistentOfPhysical > vision ;
vision . updateBotsAndPlayers ( getAIInstance ( ) , pos , 0 , 2 ) ;
std : : vector < NLMISC : : CDbgPtr < CPersistentOfPhysical > > const & bots = vision . bots ( ) ;
rejected = false ;
FOREACHC ( it , std : : vector < NLMISC : : CDbgPtr < CPersistentOfPhysical > > , bots )
{
CAIEntityPhysical const * const phys = ( * it ) - > getSpawnObj ( ) ;
if ( ! phys | | phys - > getRyzomType ( ) ! = RYZOMID : : creature )
continue ;
CSpawnBotFauna const * const fauna = NLMISC : : safe_cast < const CSpawnBotFauna * > ( phys ) ;
if ( fauna ! = NULL
& & fauna - > getPersistent ( ) . getSheet ( ) - > FaunaType ( ) = = FaunaTypePlant
& & fauna - > pos ( ) . quickDistTo ( pos . toAIVector ( ) ) < 2 )
{
rejected = true ;
break ;
}
}
} ;
# if !FINAL_VERSION
if ( rejected )
{
nlwarning ( " Solo Plant Pos Spawn Not Found at: %s " , pos . toString ( ) . c_str ( ) ) ;
}
# endif
}
else
{
uint32 tries = 100 ; // think we won't be so expensive in the average case.
while ( tries - - > 0 )
{
RYAI_MAP_CRUNCH : : CCompatibleResult res ;
grpFauna - > targetPlace ( ) - > getRandomPos ( pos ) ;
areCompatiblesWithoutStartRestriction ( pos , grpFauna - > targetPlace ( ) - > worldValidPos ( ) , getGroup ( ) . getAStarFlag ( ) , res ) ;
if ( res . isValid ( ) ) // Cool!
break ;
}
}
}
triedPos = pos . toAIVector ( ) ;
}
std : : string CBotFauna : : getOneLineInfoString ( ) const
{
return std : : string ( " Fauna bot ' " ) + getName ( ) + " ' " + " (AliasName : " + getAliasFullName ( ) + " ) " ;
}
void CBotFauna : : sheetChanged ( )
{
if ( getSpawnObj ( ) )
{
// Get bot state
RYAI_MAP_CRUNCH : : CWorldPosition botWPos = getSpawnObj ( ) - > wpos ( ) ;
CAngle spawnTheta = getSpawnObj ( ) - > theta ( ) ;
float botMeterSize = getSheet ( ) - > Scale ( ) * getSheet ( ) - > Radius ( ) ;
// :TODO: Save profile info
// If stuck bot position may be outside collision and must be recomputed
if ( isStuck ( ) | | IsRingShard )
getSpawnPos ( lastTriedPos , botWPos , CWorldContainer : : getWorldMap ( ) , spawnTheta ) ;
// Delete old bot
CMirrors : : removeEntity ( getSpawnObj ( ) - > getEntityId ( ) ) ;
setSpawn ( NULL ) ; // automatic smart pointer deletion
notifyBotDespawn ( ) ;
// Finalize spawn object creation
if ( ! finalizeSpawn ( botWPos , spawnTheta , botMeterSize ) )
return ;
// :KLUDGE: Both finalizeSpawn and finalizeSpawnFauna are called,
// sheetChanged has a strange herited meaning and may confuse future
// coders
// :TODO: Clean that mess and find a more elegant C++ solution to the
// problem
finalizeSpawnFauna ( ) ;
}
}
void CBotFauna : : triggerSetSheet ( AISHEETS : : ICreatureCPtr const & sheet )
{
// :KLUDGE: This test is there to mimick the behaviour of the client. This
// is not the better way to do this, but the client is so... well, you see
// what I mean.
if ( EGSPD : : CPeople : : Creature < = sheet - > Race ( ) & & sheet - > Race ( ) < EGSPD : : CPeople : : EndCreature )
CBot : : triggerSetSheet ( sheet ) ;
else
nlwarning ( " Trying to set a NPC sheet to a creature, sheet change canceled. " ) ;
}
//////////////////////////////////////////////////////////////////////////////
// CBotProfileGoAway //
//////////////////////////////////////////////////////////////////////////////
CBotProfileGoAway : : CBotProfileGoAway ( CProfileOwner * owner , RYAI_MAP_CRUNCH : : TAStarFlag denyFlags , float speed , CAIFaunaActivityBaseSpawnProfile * lastProfile )
: CAIBaseProfile ( )
, _Bot ( NLMISC : : safe_cast < CSpawnBot * > ( owner ) )
, _PathPos ( NLMISC : : safe_cast < CSpawnBot * > ( owner ) - > theta ( ) )
, _Speed ( speed )
, _LastProfile ( lastProfile )
, _fightGoAwayPathContainer ( denyFlags )
{
}
void CBotProfileGoAway : : beginProfile ( )
{
_LastDir = RYAI_MAP_CRUNCH : : CDirection ( RYAI_MAP_CRUNCH : : CDirection : : UNDEFINED ) ;
_fightGoAwayPathContainer . setDestination ( vp_auto , CAIVector ( _Bot - > pos ( ) ) ) ;
_Bot - > setMode ( MBEHAV : : NORMAL ) ;
}
void CBotProfileGoAway : : updateProfile ( uint ticksSinceLastUpdate )
{
H_AUTO ( BotGoAwayProfileUpdate )
CFollowPathContext fpcBotGoAwayProfileUpdate ( " BotGoAwayProfileUpdate " ) ;
if ( ! _Bot - > canMove ( ) )
return ;
bool calcDone = true ;
if ( _Decalage . isNull ( ) )
_Decalage . setX ( 1 + _Decalage . x ( ) ) ; // hum ..
RYAI_MAP_CRUNCH : : CDirection startDir ( _Decalage . x ( ) , _Decalage . y ( ) , true ) ;
// if we need to change our destination.
if ( startDir ! = _LastDir
| | _fightGoAwayPathContainer . getDestPos ( /*_Bot->size()*/ ) . hasSameFullCellId ( _Bot - > wpos ( ) ) )
{
float BestScore = - 1.f ;
RYAI_MAP_CRUNCH : : CWorldPosition BestPos ;
const RYAI_MAP_CRUNCH : : CWorldMap & worldMap = CWorldContainer : : getWorldMap ( /*_Bot->size()*/ ) ;
calcDone = false ;
sint nbStep = 0 ;
while ( nbStep < 8 )
{
// try to find a direction around startDir.
RYAI_MAP_CRUNCH : : CDirection dir ( startDir ) ;
dir . addStep ( ( RYAI_MAP_CRUNCH : : CDirection : : TDeltaDirection ) ( ( nbStep & 1 ) ? ( nbStep > > 1 ) : ( - ( nbStep > > 1 ) ) ) ) ;
const RYAI_MAP_CRUNCH : : CRootCell * rootCell = worldMap . getRootCellCst ( _Bot - > wpos ( ) . stepCell ( dir . dx ( ) , dir . dy ( ) ) ) ; // _Bot->wpos()
if ( rootCell )
{
for ( uint32 pointIndex = 0 ; pointIndex < 4 ; pointIndex + + )
{
const RYAI_MAP_CRUNCH : : CWorldPosition & wpos = rootCell - > getWorldPosition ( pointIndex ) ;
if ( wpos . isValid ( ) )
{
CCompatibleResult res ;
areCompatiblesWithoutStartRestriction ( _Bot - > wpos ( ) , wpos , _fightGoAwayPathContainer . getDenyFlags ( ) , res ) ;
if ( ! res . isValid ( ) )
{
// nlwarning("Error case avoided. Please report this warning to jvuarand.");
continue ;
}
CAIVector deltaToDest ( wpos - _Bot - > wpos ( ) ) ;
const float score = ( float ) CAIVector ( wpos - _Bot - > wpos ( ) ) . dot ( _Decalage ) ; //*deltaToDest.norm();
if ( score > BestScore
& & deltaToDest . quickNorm ( ) > 2.f ) // minimum distance requires.
{
BestScore = score ;
BestPos = wpos ;
}
}
}
}
nbStep + + ;
}
RYAI_MAP_CRUNCH : : CCompatibleResult res ;
if ( _Bot - > wpos ( ) . isValid ( ) & & BestPos . isValid ( ) )
{
areCompatiblesWithoutStartRestriction ( _Bot - > wpos ( ) , BestPos , _fightGoAwayPathContainer . denyFlags ( ) , res , true ) ;
if ( BestPos . isValid ( ) & & res . isValid ( ) )
{
_LastDir = startDir ;
_LastStartPos = _Bot - > wpos ( ) ;
calcDone = true ;
_fightGoAwayPathContainer . setDestination ( vp_auto , BestPos ) ;
}
}
}
// if we found somewhere to go, then go there ..
if ( calcDone )
{
const float dist = ( ( 1.f - _Speed ) * _Bot - > walkSpeed ( ) + _Speed * _Bot - > runSpeed ( ) ) * ticksSinceLastUpdate ;
_Decalage . normalize ( 100.f * 1000.f ) ;
_Decalage + = CAIVector ( _Bot - > pos ( ) ) ;
CFollowPath : : TFollowStatus const status = CFollowPath : : getInstance ( ) - > followPath (
_Bot ,
_PathPos ,
_fightGoAwayPathContainer ,
dist ,
dist * .71f ,
.5f ,
true ,
& _Decalage ) ;
if ( status = = CFollowPath : : FOLLOW_NO_PATH )
{
# if !FINAL_VERSION
nlwarning ( " GoAway followpath problem (!!) " ) ;
# endif
}
}
else
{
_LastDir = RYAI_MAP_CRUNCH : : CDirection ( RYAI_MAP_CRUNCH : : CDirection : : UNDEFINED ) ;
}
_Decalage = CAIVector ( 0 , 0 ) ;
}
//////////////////////////////////////////////////////////////////////////////
// CBotProfileGoAway //
//////////////////////////////////////////////////////////////////////////////
NLMISC : : CSmartPtr < IAIProfile > CBotProfileGoAwayFactory : : createAIProfile ( CProfileOwner * owner )
{
nlassert ( false ) ;
return NULL ;
}
//////////////////////////////////////////////////////////////////////////////
// Global functions //
//////////////////////////////////////////////////////////////////////////////
static const char * cyclesStateName ( CFaunaActivity : : TCycleState s )
{
switch ( s )
{
case CFaunaActivity : : CycleStateHungry : return " HUNGRY " ;
case CFaunaActivity : : CycleStateVeryHungry : return " VERY_HUNGRY " ;
case CFaunaActivity : : CycleStateStarving : return " STARVING " ;
case CFaunaActivity : : CycleStateDigesting : return " DIGESTING " ;
case CFaunaActivity : : CycleStateTired : return " TIRED " ;
case CFaunaActivity : : CycleStateVeryTired : return " VERY_TIRED " ;
case CFaunaActivity : : CycleStateExhausted : return " EXHAUSTED " ;
case CFaunaActivity : : CycleStateShaking : return " SHAKING " ;
default :
break ;
}
return " UNKNOWN STATE " ;
}