1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "server_share/r2_variables.h"
23 #include "ai_grp_npc.h"
24 #include "ai_mgr_npc.h"
25 #include "ai_bot_npc.h"
26 #include "game_share/base_types.h"
27 #include "npc_description_msg.h"
28 #include "ai_player.h"
29 #include "ai_profile_npc.h"
30 #include "server_share/r2_variables.h"
32 extern bool simulateBug(int bugId
);
34 #include "dyn_grp_inline.h"
36 using namespace MULTI_LINE_FORMATER
;
38 using namespace NLMISC
;
39 using namespace NLNET
;
41 using namespace AITYPES
;
43 extern CVariable
<uint32
> DefaultNpcAggroDist
;
44 extern CVariable
<float> DefaultEscortRange
;
46 static bool VerboseLog
=false;
47 #define LOG if (!VerboseLog) {} else nlinfo
49 // 5 minute in ticks (10 Hz)
50 uint32 DEFAULT_RESPAWN_DELAY
= 5*60*10;
52 //////////////////////////////////////////////////////////////////////////////
54 //////////////////////////////////////////////////////////////////////////////
56 uint32
CSpawnGroupNpc::_SlowUpdatePeriod
= 10;
57 vector
<uint32
> CSpawnGroupNpc::_SlowUpdateBuckets(_SlowUpdatePeriod
);
59 CSpawnGroupNpc::CSpawnGroupNpc(CPersistent
<CSpawnGroup
>& owner
)
61 , _GroupInVision(false)
63 sint32
const randomVal
= (sint32
)CTimeInterface::gameCycle()-CAIS::rand32(20);
64 _LastUpdate
= (randomVal
>=0)?randomVal
:CTimeInterface::gameCycle();
66 _LastBotUpdate
= CTimeInterface::gameCycle();
67 activityProfile().setAIProfile(new CGrpProfileNormal(this));
68 _BotUpdateTimer
.set((CAIS::rand32(40)+((intptr_t)this>>2))%20); // start with a random value.
69 resetSlowUpdateCycle();
70 _DespawnBotsWhenNoMoreHandleTimerActive
= false;
73 void CSpawnGroupNpc::setSlowUpdatePeriod(uint32 ticks
)
79 _SlowUpdatePeriod
= ticks
;
80 // Reset cycles buckets
81 _SlowUpdateBuckets
.clear();
82 _SlowUpdateBuckets
.resize(_SlowUpdatePeriod
);
84 FOREACH (itInstance
, CCont
<CAIInstance
>, CAIS::instance().AIList())
86 FOREACH (itManager
, CCont
<CManager
>, itInstance
->managers())
88 FOREACH (itGroup
, CCont
<CGroup
>, itManager
->groups())
91 CGroup
* group
= *itGroup
;
92 CSpawnGroup
* spawnGroup
= group
->getSpawnObj();
93 CSpawnGroupNpc
* spawnGroupNpc
= dynamic_cast<CSpawnGroupNpc
*>(spawnGroup
);
94 if (spawnGroupNpc
!=NULL
)
95 spawnGroupNpc
->resetSlowUpdateCycle();
101 uint32
CSpawnGroupNpc::getSlowUpdatePeriod()
103 return _SlowUpdatePeriod
;
106 void CSpawnGroupNpc::resetSlowUpdateCycle()
108 nlassert(_SlowUpdateBuckets
.size() == _SlowUpdatePeriod
);
109 // Find the lowest bucket
110 vector
<uint32
>::iterator it
= std::min_element(_SlowUpdateBuckets
.begin(), _SlowUpdateBuckets
.end());
111 // Assign it to the group
112 _SlowUpdateCycle
= (uint32
)(it
- _SlowUpdateBuckets
.begin());
113 // Fill the bucket with the group
114 *it
+= bots().size();
117 void CSpawnGroupNpc::displaySlowUpdateBuckets()
119 std::vector
<uint32
>::iterator it
, end
=_SlowUpdateBuckets
.end();
120 for (it
= _SlowUpdateBuckets
.begin(); it
!=end
; ++it
)
122 nlinfo("Bucket %d: %d", it
-_SlowUpdateBuckets
.begin(), *it
);
126 void CSpawnGroupNpc::noMoreHandle(uint32 nNbTickBeforeDespawn
)
128 _DespawnBotsWhenNoMoreHandleTimerActive
= true;
129 _DespawnBotsWhenNoMoreHandleTimer
.set(nNbTickBeforeDespawn
);
132 void CSpawnGroupNpc::handlePresent()
134 _DespawnBotsWhenNoMoreHandleTimerActive
= false;
137 void CSpawnGroupNpc::sendInfoToEGS() const
139 FOREACHC(itBot
, CCont
<CBot
>, bots())
141 CBot
const* bot
= *itBot
;
142 if (!bot
->isSpawned())
144 // :NOTE: As it can be called during CSpawnBotNpc dtor rely on dynamic cast to verify object is valid
145 CSpawnBotNpc
const* spawn
= dynamic_cast<CSpawnBotNpc
const*>(bot
->getSpawnObj());
147 spawn
->sendInfoToEGS();
151 void CSpawnGroupNpc::update()
153 H_AUTO(GrpNpcUpdate
);
155 ++AISStat::GrpTotalUpdCtr
;
156 ++AISStat::GrpNpcUpdCtr
;
158 uint32
const Dt
= CTimeInterface::gameCycle()-_LastUpdate
;
159 bool const inFight
= activityProfile().getAIProfileType()==FIGHT_NORMAL
;
161 uint32 updateTrigger
; // in tick
163 // use the lowest update time available
168 else if ( getPersistent().isRingGrp() )
172 else if (_GroupInVision
)
181 bool const haveToUpdateGroupBehaviour
= (Dt
>=updateTrigger
); // every second.
183 if (haveToUpdateGroupBehaviour
)
185 H_AUTO(GrpNpcUpdateBehaviour
);
186 // record the tick at which we ran this update (for future refference)
187 _LastUpdate
= CTimeInterface::gameCycle();
191 getPersistent().updateStateInstance();
194 // bot update()s --------------------------------------------------
196 if (haveToUpdateGroupBehaviour
)
198 H_AUTO(GrpNpcUpdateBots
);
199 _GroupInVision
= false;
201 FOREACH(first
,CCont
<CBot
>, bots())
203 CSpawnBotNpc
const* const bot
= static_cast<CBotNpc
*>(*first
)->getSpawn();
211 else // Normal behaviour
216 if (!bot
->havePlayersAround())
219 _GroupInVision
= true;
224 // TODO : hack : remove this when the "can't reach and turn arround" debility is corrected
225 bool fastUpdate
= _GroupInVision
|| inFight
;
226 bool slowUpdate
= (CTimeInterface::gameCycle()%_SlowUpdatePeriod
)==_SlowUpdateCycle
;
228 if (fastUpdate
|| slowUpdate
)
230 uint32
const botDt
= CTimeInterface::gameCycle()-_LastBotUpdate
;
231 _LastBotUpdate
= CTimeInterface::gameCycle();
233 FOREACH(first
, CCont
<CBot
>, bots())
235 CSpawnBotNpc
* const bot
= static_cast<CBotNpc
*>(*first
)->getSpawn();
236 if (bot
&& bot
->isAlive())
238 H_AUTO(GrpNpcUpdateBotsUpdate
);
242 _BotUpdateTimer
.set(10);
246 if (haveToUpdateGroupBehaviour
)
248 H_AUTO(GrpNpcUpdateGrpBehaviour
);
249 if (!activityProfile().getAISpawnProfile().isNull()) // Check if we have a behaviour.
250 activityProfile().updateProfile(Dt
); // If so, then update it !
251 // this->CBotAggroOwner::update(Dt);
254 if (_DespawnBotsWhenNoMoreHandleTimerActive
)
256 if (_DespawnBotsWhenNoMoreHandleTimer
.test())
258 _DespawnBotsWhenNoMoreHandleTimerActive
= false;
264 void CSpawnGroupNpc::stateChange(CAIState
const* oldState
, CAIState
const* newState
)
266 // Find changing group profiles.
268 setProfileParameters(getPersistent().profileParameters());
270 IAIProfileFactory
* moveProfile
= newState
->moveProfile();
271 IAIProfileFactory
* actProfile
= newState
->activityProfile();
273 mergeProfileParameters(newState
->profileParameters());
275 FOREACHC(it
, CCont
<CAIStateProfile
>, newState
->profiles())
277 if (!it
->testCompatibility(getPersistent()))
280 if (it
->moveProfile()!= RYAI_GET_FACTORY(CGrpProfileNoChangeFactory
))
281 moveProfile
= it
->moveProfile();
283 if (it
->activityProfile()!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory
))
284 actProfile
= it
->activityProfile();
286 mergeProfileParameters(it
->profileParameters());
294 if (!newState
->isPositional() && oldState
->isPositional())
296 // begin of punctual state
297 // need to backup the current profiles
298 _PunctualHoldActivityProfile
= activityProfile();
299 _PunctualHoldMovingProfile
= movingProfile();
301 activityProfile() = CProfilePtr();
302 movingProfile() = CProfilePtr();
306 if (newState
->isPositional() && !oldState
->isPositional())
308 // end of punctual state
309 // need to restore the backuped profile
310 activityProfile() = _PunctualHoldActivityProfile
;
311 movingProfile() = _PunctualHoldMovingProfile
;
313 _PunctualHoldActivityProfile
= CProfilePtr();
314 _PunctualHoldMovingProfile
= CProfilePtr();
316 // resume the profiles
317 activityProfile().getAISpawnProfile()->resumeProfile();
318 movingProfile().getAISpawnProfile()->resumeProfile();
323 // normal behavior, transition from positionnal to positionnal state
324 if (moveProfile
!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory
))
325 setMoveProfileFromStateMachine(moveProfile
);
327 if (actProfile
!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory
))
328 setActivityProfileFromStateMachine(actProfile
);
334 // iterate through bots changing their chat.
335 for (CCont
<CBot
>::iterator it
=bots().begin(), itEnd
=bots().end();it
!=itEnd
;++it
)
337 CBotNpc
*const botNpc
=static_cast<CBotNpc
*>(*it
);
338 if (!botNpc
->isSpawned())
340 botNpc
->getSpawn()->updateChat(newState
);
344 void CSpawnGroupNpc::spawnBots(const std::string
&name
)
347 ucName
.fromUtf8(name
);
349 FOREACH(itBot
, CCont
<CBot
>, bots())
352 if (!bot
->isSpawned()) {
357 CSpawnBot
*spawnBot
= bot
->getSpawnObj();
360 TDataSetRow row
= spawnBot
->dataSetRow();
361 NLNET::CMessage
msgout("CHARACTER_NAME");
363 msgout
.serial(ucName
);
364 sendMessageViaMirror("IOS", msgout
);
365 spawnBot
->getPersistent().setCustomName(ucName
);
370 CEntityId id
= bot
->getSpawnObj()->getEntityId();
371 sint32 x
= bot
->getSpawnObj()->pos().x();
372 sint32 y
= bot
->getSpawnObj()->pos().y();
373 sint32 z
= bot
->getSpawnObj()->pos().h();
374 float t
= bot
->getSpawnObj()->pos().theta().asRadians();
377 NLMISC::TGameCycle tick
= CTickEventHandler::getGameCycle() + 1;
378 CMessage
msgout2("ENTITY_TELEPORTATION");
379 msgout2
.serial( id
);
384 msgout2
.serial( tick
);
385 msgout2
.serial( cont
);
386 msgout2
.serial( _Cell
);
387 msgout2
.serial( slide
);
389 sendMessageViaMirror("GPMS", msgout2
);
395 void CSpawnGroupNpc::spawnBots()
397 FOREACH(itBot
, CCont
<CBot
>, bots())
400 if (!bot
->isSpawned()) {
404 CEntityId id
= bot
->getSpawnObj()->getEntityId();
405 sint32 x
= bot
->getSpawnObj()->pos().x();
406 sint32 y
= bot
->getSpawnObj()->pos().y();
407 sint32 z
= bot
->getSpawnObj()->pos().h();
408 float t
= bot
->getSpawnObj()->pos().theta().asRadians();
411 NLMISC::TGameCycle tick
= CTickEventHandler::getGameCycle() + 1;
412 CMessage
msgout2("ENTITY_TELEPORTATION");
413 msgout2
.serial( id
);
418 msgout2
.serial( tick
);
419 msgout2
.serial( cont
);
420 msgout2
.serial( _Cell
);
421 msgout2
.serial( slide
);
423 sendMessageViaMirror("GPMS", msgout2
);
429 void CSpawnGroupNpc::despawnBots(bool immediately
)
431 FOREACH(itBot
, CCont
<CBot
>, bots())
433 CBot
* const bot
= *itBot
;
434 if (!bot
->isSpawned())
437 if (TheDataset
.getOnlineTimestamp( bot
->getSpawnObj()->dataSetRow()) >= CTickEventHandler::getGameCycle())
438 nlwarning("Bots %s:%s spawn/despawn in the same tick ! despawn ignored", getPersistent().getName().c_str(), bot
->getName().c_str());
442 if (getPersistent()._AutoDestroy
)
443 getPersistent().getOwner()->groups().removeChildByIndex(getPersistent().getChildIndex());
446 CGroupNpc
& CSpawnGroupNpc::getPersistent() const
448 return static_cast<CGroupNpc
&>(CSpawnGroup::getPersistent());
451 //////////////////////////////////////////////////////////////////////////////
453 //////////////////////////////////////////////////////////////////////////////
455 CGroupNpc::CGroupNpc(CMgrNpc
* mgr
, CAIAliasDescriptionNode
* aliasTree
, RYAI_MAP_CRUNCH::TAStarFlag denyFlags
)
456 : CGroup(mgr
, denyFlags
, aliasTree
)
458 , CPersistentStateInstance(*mgr
->getStateMachine())
460 _BotsAreNamed
= true;
462 _PlayerAttackable
= false;
463 _BotAttackable
= false;
468 CGroupNpc::CGroupNpc(CMgrNpc
* mgr
, uint32 alias
, std::string
const& name
, RYAI_MAP_CRUNCH::TAStarFlag denyFlags
)
469 : CGroup(mgr
, denyFlags
, alias
, name
)
471 , CPersistentStateInstance(*mgr
->getStateMachine())
473 _BotsAreNamed
= true;
475 _PlayerAttackable
= false;
476 _BotAttackable
= false;
480 CGroupNpc::~CGroupNpc()
482 // avoid re-deletion by despawn
483 _AutoDestroy
= false;
484 if (isSpawned()) // to avoid bad CDbgPtr link interpretation
487 // clear all persistent state instance
488 while (!_PSIChilds
.empty())
490 _PSIChilds
.back()->setParentStateInstance(NULL
);
496 std::string
CGroupNpc::getOneLineInfoString() const
498 return std::string("NPC Group '") + getName() + "'";
501 std::vector
<std::string
> CGroupNpc::getMultiLineInfoString() const
503 std::vector
<std::string
> container
;
504 std::vector
<std::string
> strings
;
506 pushTitle(container
, "CGroupNpc");
507 strings
= CGroup::getMultiLineInfoString();
508 FOREACHC(itString
, std::vector
<std::string
>, strings
)
509 pushEntry(container
, *itString
);
510 pushEntry(container
, "faction=" + (faction().empty()?string("<none>"):faction().toString()));
511 pushEntry(container
, "friends=" + (friendFaction().empty()?string("<none>"):friendFaction().toString()));
512 pushEntry(container
, "ennemies=" + (ennemyFaction().empty()?string("<none>"):ennemyFaction().toString()));
513 pushEntry(container
, NLMISC::toString("attackable: player=%s bot=%s", _PlayerAttackable
?"yes":"no", _BotAttackable
?"yes":"no"));
514 pushEntry(container
, "state=" + (getState()?getState()->getName():string("<null>")));
515 pushFooter(container
);
520 CMgrNpc
& CGroupNpc::mgr() const
522 return *static_cast<CMgrNpc
*>(getOwner());
525 void CGroupNpc::updateDependencies(CAIAliasDescriptionNode
const& aliasTree
, CAliasTreeOwner
* aliasTreeOwner
)
527 switch(aliasTree
.getType())
531 CAIEventReaction
* const eventPtr
= NLMISC::safe_cast
<CAIEventReaction
*>(mgr().getStateMachine()->eventReactions().getChildByAlias(aliasTree
.getAlias()));
535 eventPtr
->setType(CAIEventReaction::FixedGroup
);
536 eventPtr
->setGroup(getAlias());
542 IAliasCont
* CGroupNpc::getAliasCont(TAIType type
)
549 case AITypeFolder
: // Nothing ..
555 CAliasTreeOwner
* CGroupNpc::createChild(IAliasCont
* cont
, CAIAliasDescriptionNode
* aliasTree
)
557 CAliasTreeOwner
* child
= NULL
;
559 switch (aliasTree
->getType())
561 case AITypeOutpostBuilding
:
563 child
= new CBotNpc(this, aliasTree
);
572 cont
->addAliasChild(child
);
576 CSmartPtr
<CSpawnGroup
> CGroupNpc::createSpawnGroup()
578 return new CSpawnGroupNpc(*this);
581 void CGroupNpc::serviceEvent (const CServiceEvent
&info
)
583 CGroup::serviceEvent(info
);
586 if ( (info
.getServiceName() == "EGS") && (info
.getEventType() == CServiceEvent::SERVICE_DOWN
) )
588 // If the Group of NPC has handles on itself
589 if (!_Handles
.empty())
591 // Remove all handles
594 if (getSpawnObj() != NULL
)
596 if (getSpawnObj()->isGroupAlive()) // is there some bots spawned
598 getSpawnObj()->noMoreHandle(_DespawnTimeWhenNoMoreHandle
);
604 if ((info
.getServiceName() == "EGS") && (info
.getEventType() == CServiceEvent::SERVICE_UP
))
606 processStateEvent(getEventContainer().EventEGSUp
);
612 bool CGroupNpc::spawn ()
616 setStartState(getStartState()); // stateInstance.
618 // inform the EGS of our existence - simulate connection of EGS
619 serviceEvent (CServiceEvent(NLNET::TServiceId(0),std::string("EGS"),CServiceEvent::SERVICE_UP
));
623 CCont
<CBot
>::iterator
first(bots().begin()), last(bots().end());
624 for (; first
!= last
; ++first
)
626 CBotNpc
*bot
= static_cast<CBotNpc
*>(*first
);
627 // ok, we can spawn this bot
636 void CGroupNpc::despawnGrp()
638 CGroup::despawnGrp();
641 void CGroupNpc::clearParameters()
643 _PlayerAttackable
= false;
644 _BotAttackable
= false;
646 _AggroDist
= DefaultNpcAggroDist
;
647 setEscortTeamId(CTEAM::InvalidTeamId
);
648 setEscortRange(DefaultEscortRange
);
649 respawnTime() = (uint32
) DEFAULT_RESPAWN_DELAY
;
650 despawnTime() = (uint32
) DEFAULT_RESPAWN_DELAY
/ 4;
653 // Parse a paremeter for this group.
654 void CGroupNpc::addParameter(std::string
const& parameter
)
656 static std::string
ATTACKABLE("attackable");
657 static std::string
PLAYER_ATTACKABLE("player_attackable");
658 static std::string
BOT_ATTACKABLE("bot_attackable");
659 static std::string
BADGUY("bad guy");
660 static std::string
AGGRO_RANGE("aggro range");
661 static std::string
ESCORT_RANGE("escort range");
662 static std::string
RESPAWN_TIME("respawn time");
663 static std::string
DESPAWN_TIME("despawn time");
664 static std::string
RING("ring");
665 static std::string
DENIED_ASTAR_FLAGS("denied_astar_flags");
667 std::string key
, tail
;
670 std::string p
= NLMISC::toLowerAscii(parameter
);
671 AI_SHARE::stringToKeywordAndTail(p
, key
, tail
);
680 if (key
== BOT_ATTACKABLE
)
682 _BotAttackable
= true;
686 if (key
== ATTACKABLE
|| key
== PLAYER_ATTACKABLE
)
688 // the bots are attackable !
689 _PlayerAttackable
= true;
690 if (!IsRingShard
) // In Ring shard, BotAttackable means attackable by bot not vulnerable
692 // attackable implie vulnerable!
693 _BotAttackable
= true;
700 // the bots are bad guys! they will attack players in their aggro range.
703 NLMISC::fromString(tail
, _AggroDist
);
704 // bad guy imply attackable!
705 _PlayerAttackable
= true;
706 // bad guy imply vulnerable!
707 _BotAttackable
= true;
711 nlwarning("GrpNpc::addParameter : in parameter '%s', missing agrodist !", parameter
.c_str());
716 if (key
== AGGRO_RANGE
)
720 NLMISC::fromString(tail
, _AggroDist
);
725 nlwarning("GrpNpc::addParameter : in parameter '%s', missing agrodist !", parameter
.c_str());
730 if (key
== ESCORT_RANGE
)
734 float range
= float(atof(tail
.c_str()));
735 LOG("Setting range to %f", range
);
736 setEscortRange(range
);
740 nlwarning("GrpNpc::addParameter : in parameter '%s', missing escort range !", parameter
.c_str());
745 if (key
== RESPAWN_TIME
)
749 uint32 respawntime
= NLMISC::atoui(tail
.c_str());
750 // TODO:kxu: fix CAITimer!
751 if (respawntime
> 0x7fffffff)
752 respawntime
= 0x7fffffff; // AI timers do not treat properly delta times greater than the max signed int
753 LOG("Setting respawn time to %u", respawntime
);
754 respawnTime()=respawntime
;
758 nlwarning("GrpNpc::addParameter : in parameter '%s', missing respawn time !", parameter
.c_str());
763 if (key
== DESPAWN_TIME
)
767 uint32 despawntime
= NLMISC::atoui(tail
.c_str());
768 // TODO:kxu: fix CAITimer!
769 if (despawntime
> 0x7fffffff)
770 despawntime
= 0x7fffffff; // AI timers do not treat properly delta times greater than the max signed int
771 LOG("Setting despawn time to %u", despawntime
);
772 despawnTime()=despawntime
;
776 nlwarning("GrpNpc::addParameter : in parameter '%s', missing despawn time !", parameter
.c_str());
781 if (key
== DENIED_ASTAR_FLAGS
)
785 RYAI_MAP_CRUNCH::TAStarFlag flags
= RYAI_MAP_CRUNCH::Nothing
;
786 vector
<string
> sFlags
;
787 NLMISC::splitString(tail
, "|", sFlags
);
788 FOREACHC(it
, vector
<string
>, sFlags
)
790 flags
= RYAI_MAP_CRUNCH::TAStarFlag( flags
| RYAI_MAP_CRUNCH::toAStarFlag(*it
) );
793 LOG("Setting denied AStar flags to (%s) = %u", tail
.c_str(), flags
);
798 nlwarning("GrpNpc::addParameter : in parameter '%s', missing denied AStar flags !", parameter
.c_str());
803 if (parameter
.empty())
806 nlwarning("CAIBotNpc::addParameter unknown parameter '%s'", parameter
.c_str());
810 void CGroupNpc::addHpUpTrigger(float threshold
, int eventId
)
812 _hpUpTriggers
.insert(std::make_pair(threshold
, eventId
));
815 void CGroupNpc::delHpUpTrigger(float threshold
, int eventId
)
817 CGroupNpc::THpTriggerList
& hpTriggers
= _hpUpTriggers
;
818 CGroupNpc::THpTriggerList::iterator first
, last
, trigger
;
819 first
= hpTriggers
.lower_bound(threshold
);
820 last
= hpTriggers
.upper_bound(threshold
);
822 for (; first
!=last
; ++first
)
824 if (first
->second
==eventId
)
828 hpTriggers
.erase(first
);
831 void CGroupNpc::addHpDownTrigger(float threshold
, int eventId
)
833 _hpDownTriggers
.insert(std::make_pair(threshold
, eventId
));
836 void CGroupNpc::delHpDownTrigger(float threshold
, int eventId
)
838 CGroupNpc::THpTriggerList
& hpTriggers
= _hpDownTriggers
;
839 CGroupNpc::THpTriggerList::iterator first
, last
, trigger
;
840 first
= hpTriggers
.lower_bound(threshold
);
841 last
= hpTriggers
.upper_bound(threshold
);
843 for (; first
!=last
; ++first
)
845 if (first
->second
==eventId
)
849 hpTriggers
.erase(first
);
852 void CGroupNpc::addHpUpTrigger(float threshold
, std::string cbFunc
)
854 _hpUpTriggers2
.insert(std::make_pair(threshold
, cbFunc
));
857 void CGroupNpc::delHpUpTrigger(float threshold
, std::string cbFunc
)
859 CGroupNpc::THpTriggerList2
& hpTriggers
= _hpUpTriggers2
;
860 CGroupNpc::THpTriggerList2::iterator first
, last
, trigger
;
861 first
= hpTriggers
.lower_bound(threshold
);
862 last
= hpTriggers
.upper_bound(threshold
);
864 for (; first
!=last
; ++first
)
866 if (first
->second
==cbFunc
)
870 hpTriggers
.erase(first
);
873 void CGroupNpc::addHpDownTrigger(float threshold
, std::string cbFunc
)
875 _hpDownTriggers2
.insert(std::make_pair(threshold
, cbFunc
));
878 void CGroupNpc::delHpDownTrigger(float threshold
, std::string cbFunc
)
880 CGroupNpc::THpTriggerList2
& hpTriggers
= _hpDownTriggers2
;
881 CGroupNpc::THpTriggerList2::iterator first
, last
, trigger
;
882 first
= hpTriggers
.lower_bound(threshold
);
883 last
= hpTriggers
.upper_bound(threshold
);
885 for (; first
!=last
; ++first
)
887 if (first
->second
==cbFunc
)
891 hpTriggers
.erase(first
);
894 bool CGroupNpc::haveHpTriggers()
896 return (_hpUpTriggers
.size()+_hpDownTriggers
.size()+_hpUpTriggers2
.size()+_hpDownTriggers2
.size())>0;
899 void CGroupNpc::hpTriggerCb(float oldVal
, float newVal
)
903 CGroupNpc::THpTriggerList::const_iterator first
, last
, trigger
, triggerProcessed
;
904 first
= _hpUpTriggers
.upper_bound(oldVal
);
905 last
= _hpUpTriggers
.upper_bound(newVal
);
906 for (trigger
=first
; trigger
!=last
;)
908 triggerProcessed
= trigger
;
910 processStateEvent(getEventContainer().EventUserEvent
[triggerProcessed
->second
]);
912 CGroupNpc::THpTriggerList2::const_iterator first2
, last2
, trigger2
, triggerProcessed2
;
913 first2
= _hpUpTriggers2
.upper_bound(oldVal
);
914 last2
= _hpUpTriggers2
.upper_bound(newVal
);
915 for (trigger2
=first2
; trigger2
!=last2
;)
917 triggerProcessed2
= trigger2
;
919 callScriptCallBack(this, NLMISC::CStringMapper::map(triggerProcessed2
->second
));
924 CGroupNpc::THpTriggerList::const_iterator first
, last
, trigger
, triggerProcessed
;
925 first
= _hpDownTriggers
.lower_bound(newVal
);
926 last
= _hpDownTriggers
.lower_bound(oldVal
);
927 for (trigger
=first
; trigger
!=last
;)
929 triggerProcessed
= trigger
;
931 processStateEvent(getEventContainer().EventUserEvent
[triggerProcessed
->second
]);
933 CGroupNpc::THpTriggerList2::const_iterator first2
, last2
, trigger2
, triggerProcessed2
;
934 first2
= _hpDownTriggers2
.lower_bound(newVal
);
935 last2
= _hpDownTriggers2
.lower_bound(oldVal
);
936 for (trigger2
=first2
; trigger2
!=last2
;)
938 triggerProcessed2
= trigger2
;
940 callScriptCallBack(this, NLMISC::CStringMapper::map(triggerProcessed2
->second
));
945 void CGroupNpc::addNamedEntityListener(std::string
const& name
, std::string
const& prop
, int event
)
947 _namedEntityListeners
.insert(std::make_pair(std::make_pair(name
, prop
), event
));
948 CNamedEntityManager::getInstance()->get(name
).addListenerGroup(prop
, this);
951 void CGroupNpc::delNamedEntityListener(std::string
const& name
, std::string
const& prop
, int event
)
953 TNamedEntityListenerList::iterator first
, last
, listener
;
954 listener
= _namedEntityListeners
.lower_bound(std::make_pair(name
, prop
));
955 last
= _namedEntityListeners
.upper_bound(std::make_pair(name
, prop
));
956 while (listener
!=last
)
958 if (listener
->second
==event
)
960 _namedEntityListeners
.erase(listener
);
961 CNamedEntityManager::getInstance()->get(name
).delListenerGroup(prop
, this);
968 void CGroupNpc::addNamedEntityListener(std::string
const& name
, std::string
const& prop
, std::string functionName
)
970 _namedEntityListeners2
.insert(std::make_pair(std::make_pair(name
, prop
), functionName
));
971 CNamedEntityManager::getInstance()->get(name
).addListenerGroup(prop
, this);
974 void CGroupNpc::delNamedEntityListener(std::string
const& name
, std::string
const& prop
, std::string functionName
)
976 TNamedEntityListenerList2::iterator first
, last
, listener
;
977 listener
= _namedEntityListeners2
.lower_bound(std::make_pair(name
, prop
));
978 last
= _namedEntityListeners2
.upper_bound(std::make_pair(name
, prop
));
979 while (listener
!=last
)
981 if (listener
->second
==functionName
)
983 _namedEntityListeners2
.erase(listener
);
984 CNamedEntityManager::getInstance()->get(name
).delListenerGroup(prop
, this);
991 void CGroupNpc::namedEntityListenerCb(std::string
const& name
, std::string
const& prop
)
993 TNamedEntityListenerList::iterator first
, last
, listener
;
994 first
= _namedEntityListeners
.lower_bound(std::make_pair(name
, prop
));
995 last
= _namedEntityListeners
.upper_bound(std::make_pair(name
, prop
));
996 for (listener
=first
; listener
!=last
; ++listener
)
998 processStateEvent(getEventContainer().EventUserEvent
[listener
->second
]);
1000 TNamedEntityListenerList2::iterator first2
, last2
, listener2
;
1001 first2
= _namedEntityListeners2
.lower_bound(std::make_pair(name
, prop
));
1002 last2
= _namedEntityListeners2
.upper_bound(std::make_pair(name
, prop
));
1003 std::queue
<NLMISC::TStringId
> listeners
;
1004 for (listener2
=first2
; listener2
!=last2
; ++listener2
)
1005 listeners
.push(NLMISC::CStringMapper::map(listener2
->second
));
1006 while(!listeners
.empty())
1008 callScriptCallBack(this, listeners
.front());
1013 //--------------------------------------------------------------------
1015 //--------------------------------------------------------------------
1016 void CGroupNpc::addHandle(TDataSetRow playerRowId
, uint32 missionAlias
, uint32 DespawnTimeInTick
)
1018 // If the group is not spawned -> spawn it
1020 // Save some states when adding first handle
1021 if (_Handles
.empty())
1023 _AutoSpawnWhenNoMoreHandle
= isAutoSpawn();
1027 // There is always a spawned object for group
1028 if (getSpawnObj() != NULL
)
1030 if (! getSpawnObj()->isGroupAlive() ) // if no bots spawned
1032 getSpawnObj()->spawnBots();
1034 getSpawnObj()->handlePresent();
1036 // Register the handle
1038 h
.MissionAlias
= missionAlias
;
1039 h
.PlayerRowId
= playerRowId
;
1041 set
<SHandle
>::const_iterator it
= _Handles
.find(h
);
1043 if (it
!= _Handles
.end())
1045 nlwarning("CGroupNpc::addHandle handle already added player:%d missionAlias:%d", playerRowId
.getIndex(), missionAlias
);
1052 _DespawnTimeWhenNoMoreHandle
= DespawnTimeInTick
;
1054 CHandledAIGroupSpawnedMsg msg
;
1055 msg
.PlayerRowId
= playerRowId
;
1056 msg
.MissionAlias
= missionAlias
;
1057 msg
.GroupAlias
= getAlias();
1061 //--------------------------------------------------------------------
1063 //--------------------------------------------------------------------
1064 void CGroupNpc::delHandle(TDataSetRow playerRowId
, uint32 missionAlias
)
1066 // Unregister the handle
1068 h
.MissionAlias
= missionAlias
;
1069 h
.PlayerRowId
= playerRowId
;
1071 set
<SHandle
>::iterator it
= _Handles
.find(h
);
1072 if (it
!= _Handles
.end())
1078 // With the new queue_* generation system multiple handle_release can be done -> do not flood with this message
1079 // nlwarning("CGroupNpc::delHandle handle not found player:%d missionAlias:%d", playerRowId.getIndex(), missionAlias);
1082 // If no more handle despawn the group and restore saved variables
1083 if (_Handles
.empty())
1085 if (getSpawnObj() != NULL
)
1087 if (getSpawnObj()->isGroupAlive()) // is there some bots spawned
1089 getSpawnObj()->noMoreHandle(_DespawnTimeWhenNoMoreHandle
);
1092 setAutoSpawn(_AutoSpawnWhenNoMoreHandle
);
1095 CHandledAIGroupDespawnedMsg msg
;
1096 msg
.PlayerRowId
= playerRowId
;
1097 msg
.MissionAlias
= missionAlias
;
1098 msg
.GroupAlias
= getAlias();
1102 //--------------------------------------------------------------------
1103 // the default display()
1104 //--------------------------------------------------------------------
1105 std::string
CSpawnGroupNpc::buildDebugString(uint idx
) const
1108 return "No debug info";
1109 return std::string();
1112 std::string
CGroupNpc::buildDebugString(uint idx
) const
1117 case 0: return "-- CGroupNpc -----------------------------";
1122 s
+= "id=" + getIndexString();
1123 s
+= " alias=" + getAliasString();
1124 s
+= " info=<not spawned>";
1125 s
+= " name=" + getName();
1129 s
+= "id=" + getIndexString();
1130 s
+= " alias="+getAliasString();
1131 s
+= " info='" + getSpawnObj()->buildDebugString(0) + "'";
1132 s
+= " name="+getName();
1137 return NLMISC::toString(": %s :", getAliasNode()->getName().c_str()) + buidStateInstanceDebugString();
1139 return NLMISC::toString(" User Timers: %s %s %s %s",
1140 userTimer(0).toString().c_str(),
1141 userTimer(1).toString().c_str(),
1142 userTimer(2).toString().c_str(),
1143 userTimer(3).toString().c_str());
1145 if (getEscortTeamId() != CTEAM::InvalidTeamId
)
1146 return NLMISC::toString(" Escorted by team %u", getEscortTeamId());
1148 return std::string(" Not escorted");
1149 case 5: return "----------------------------------------";
1151 return std::string();
1154 void CGroupNpc::display(CStringWriter
&stringWriter
) const
1157 nlstopex(("not implemented"));
1161 CAIS::CCounter
& CGroupNpc::getSpawnCounter()
1163 return CAIS::instance()._NpcBotCounter
;
1166 void CGroupNpc::lastBotDespawned()
1169 processStateEvent(getEventContainer().EventLastBotDespawned
);
1172 void CGroupNpc::firstBotSpawned()
1174 setFirstBotSpawned();
1177 void CGroupNpc::setStateAlias(const std::string
&state
, uint32 alias
)
1179 TStatesToAlias::iterator it
=_StatesToAlias
.find(state
);
1180 if (it
!= _StatesToAlias
.end())
1183 _StatesToAlias
.insert(make_pair(state
, alias
));
1186 uint32
CGroupNpc::getStateAlias(const std::string
&state
)
1188 TStatesToAlias::iterator it
=_StatesToAlias
.find(state
);
1189 if (it
!= _StatesToAlias
.end())
1194 void CGroupNpc::setStateEventAlias(const std::string
&stateEvent
, uint32 alias
)
1196 TStatesToAlias::iterator it
=_StateEventToAlias
.find(stateEvent
);
1197 if (it
!= _StateEventToAlias
.end())
1200 _StateEventToAlias
.insert(make_pair(stateEvent
, alias
));
1203 uint32
CGroupNpc::getStateEventAlias(const std::string
&state
)
1205 TStatesToAlias::iterator it
=_StateEventToAlias
.find(state
);
1206 if (it
!= _StateEventToAlias
.end())
1212 void CGroupNpc::setColour(uint8 colour
)
1214 FOREACH(itBot
, CCont
<CBot
>, bots())
1216 CBotNpc
* bot
= static_cast<CBotNpc
*>(*itBot
);
1218 bot
->setColour(colour
);
1222 void CGroupNpc::setOutpostSide(OUTPOSTENUMS::TPVPSide side
)
1224 FOREACH(itBot
, CCont
<CBot
>, bots())
1226 CBotNpc
* bot
= static_cast<CBotNpc
*>(*itBot
);
1228 bot
->setOutpostSide(side
);
1232 void CGroupNpc::setOutpostFactions(const std::string
&alias
, OUTPOSTENUMS::TPVPSide side
)
1234 // Attack only the declared ennemies of the outpost
1235 if (side
== OUTPOSTENUMS::OutpostOwner
)
1238 faction ().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias
.c_str()));
1239 friendFaction().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias
.c_str()));
1240 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias
.c_str()));
1242 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:attacker", alias
.c_str()));
1244 if (side
== OUTPOSTENUMS::OutpostAttacker
)
1247 faction ().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias
.c_str()));
1248 friendFaction().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias
.c_str()));
1249 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias
.c_str()));
1251 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:defender", alias
.c_str()));
1255 void CGroupNpc::setFactionAttackableAbove(std::string faction
, sint32 threshold
, bool botAttackable
)
1258 _FactionAttackableAbove
.insert(std::make_pair(faction
, threshold
));
1260 _FactionAttackableAbove
.erase(std::make_pair(faction
, threshold
));
1263 void CGroupNpc::setFactionAttackableBelow(std::string faction
, sint32 threshold
, bool botAttackable
)
1266 _FactionAttackableBelow
.insert(std::make_pair(faction
, threshold
));
1268 _FactionAttackableBelow
.erase(std::make_pair(faction
, threshold
));
1271 bool CGroupNpc::isFactionAttackable(std::string faction
, sint32 fame
)
1273 TFactionAttackableSet::const_iterator it
, itEnd
;
1274 for (it
=_FactionAttackableAbove
.begin(), itEnd
=_FactionAttackableAbove
.end(); it
!=itEnd
; ++it
)
1275 if (faction
==it
->first
&& fame
>it
->second
)
1277 for (it
=_FactionAttackableBelow
.begin(), itEnd
=_FactionAttackableBelow
.end(); it
!=itEnd
; ++it
)
1278 if (faction
==it
->first
&& fame
<it
->second
)
1283 void CGroupNpc::stateChange(CAIState
const* oldState
, CAIState
const* newState
)
1286 getSpawnObj()->stateChange(oldState
, newState
);
1289 void CGroupNpc::setEvent(uint eventId
)
1291 nlassert(eventId
<10);
1292 if (eventId
>= 10) return;
1293 processStateEvent(getEventContainer().EventUserEvent
[eventId
]);
1296 //////////////////////////////////////////////////////////////////////////////
1298 //////////////////////////////////////////////////////////////////////////////
1300 //---------------------------------------------------------------------------------------
1301 // Control over verbose nature of logging
1302 //---------------------------------------------------------------------------------------
1304 NLMISC_COMMAND(verboseNPCGrp
,"Turn on or off or check the state of verbose npc group logging","")
1310 StrToBool (VerboseLog
, args
[0]);
1312 nlinfo("VerboseLogging is %s",VerboseLog
?"ON":"OFF");
1316 NLMISC_COMMAND(NpcGroupSlowUpdatePeriod
, "Slow update period of the NPC groups","[<period_ticks>]")
1318 if (args
.size()==0 || args
.size()==1)
1323 NLMISC::fromString(args
[0], ticks
);
1325 CSpawnGroupNpc::setSlowUpdatePeriod(ticks
);
1329 log
.displayNL("Slow update period of the NPC groups is %d ticks", CSpawnGroupNpc::getSlowUpdatePeriod());
1335 #include "event_reaction_include.h"